webglimpse
Version:
Webglimpse is a data visualization library for the web.
1,405 lines (1,396 loc) • 634 kB
JavaScript
import moment from 'moment';
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Alias for more readable access to static constants
let GL = WebGLRenderingContext;
function hasval(value) {
// Double-equals is weird: ( undefined == null ) is true
return (value != null);
}
function isNumber(value) {
return typeof (value) === 'number';
}
function isString(value) {
return typeof (value) === 'string';
}
function isEmpty(array) {
return (array.length === 0);
}
function notEmpty(array) {
return (array.length > 0);
}
function alwaysTrue() {
return true;
}
function alwaysFalse() {
return false;
}
function constantFn(value) {
return function () {
return value;
};
}
function log10(x) {
return Math.log(x) * (1.0 / Math.LN10);
}
function order(x) {
return Math.floor(log10(x) + 1e-12);
}
function clamp$1(xMin, xMax, x) {
return Math.max(xMin, Math.min(xMax, x));
}
function copyArray(values) {
return values.slice(0);
}
function ensureCapacityFloat32(buffer, minNewCapacity) {
// if minNewCapacity is NaN, null, or undefined, don't try to resize the buffer
if (!minNewCapacity || buffer.length >= minNewCapacity) {
return buffer;
}
else {
const newCapacity = Math.max(minNewCapacity, 2 * buffer.length);
return new Float32Array(newCapacity);
}
}
function ensureCapacityUint32(buffer, minNewCapacity) {
// if minNewCapacity is NaN, null, or undefined, don't try to resize the buffer
if (!minNewCapacity || buffer.length >= minNewCapacity) {
return buffer;
}
else {
const newCapacity = Math.max(minNewCapacity, 2 * buffer.length);
return new Uint32Array(newCapacity);
}
}
function ensureCapacityUint16(buffer, minNewCapacity) {
// if minNewCapacity is NaN, null, or undefined, don't try to resize the buffer
if (!minNewCapacity || buffer.length >= minNewCapacity) {
return buffer;
}
else {
const newCapacity = Math.max(minNewCapacity, 2 * buffer.length);
return new Uint16Array(newCapacity);
}
}
let getObjectId = (function () {
const keyName = 'webglimpse_ObjectId';
let nextValue = 0;
return function (obj) {
let value = obj[keyName];
if (!hasval(value)) {
value = (nextValue++).toString();
obj[keyName] = value;
}
return value;
};
})();
function concatLines(...lines) {
return lines.join('\n');
}
function parseTime_PMILLIS(time_ISO8601) {
// Moment's lenient parsing is way too lenient -- but its strict parsing is a little too
// strict, so we have to try several possible formats.
//
// We could pass in multiple formats to try all at once, but Moment's docs warn that that
// can be slow. Plus, we expect some formats to be more common than others, so we can make
// the common formats fast at the expense of the less common ones.
//
let m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ssZZ', true);
if (m.isValid()) {
return m.valueOf();
}
m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ss.SSSZZ', true);
if (m.isValid()) {
return m.valueOf();
}
m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ss.SSZZ', true);
if (m.isValid()) {
return m.valueOf();
}
m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ss.SZZ', true);
if (m.isValid()) {
return m.valueOf();
}
throw new Error('Failed to parse time-string: \'' + time_ISO8601 + '\'');
}
/**
* Formats a timestamp from posix-millis into the format 'YYYY-MM-DDThh:mm:ss.SSSZZ' (for
* example '2014-01-01T00:00:00.000Z').
*
* The input value is effectively truncated (not rounded!) to milliseconds. So for example,
* formatTime_ISO8601(12345.999) will return '1970-01-01T00:00:12.345Z' -- exactly the same
* as for an input of 12345.
*
*/
function formatTime_ISO8601(time_PMILLIS) {
return moment(time_PMILLIS).toISOString();
}
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class CacheEntry {
constructor(value) {
this.touched = false;
this.value = value;
}
}
class Cache {
constructor(helper) {
this.helper = helper;
this.map = {};
}
value(key) {
if (!this.map.hasOwnProperty(key)) {
this.map[key] = new CacheEntry(this.helper.create(key));
}
const en = this.map[key];
en.touched = true;
return en.value;
}
clear() {
for (const k in this.map) {
if (this.map.hasOwnProperty(k)) {
this.helper.dispose(this.map[k].value, k);
}
}
this.map = {};
}
remove(key) {
if (this.map.hasOwnProperty(key)) {
this.helper.dispose(this.map[key].value, key);
delete this.map[key];
}
}
removeAll(keys) {
for (let i = 0; i < keys.length; i++) {
this.remove(keys[i]);
}
}
retain(key) {
const newMap = {};
if (this.map.hasOwnProperty(key)) {
newMap[key] = this.map[key];
delete this.map[key];
}
for (const k in this.map) {
if (this.map.hasOwnProperty(k)) {
this.helper.dispose(this.map[k].value, k);
}
}
this.map = newMap;
}
retainAll(keys) {
const newMap = {};
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
if (this.map.hasOwnProperty(k)) {
newMap[k] = this.map[k];
delete this.map[k];
}
}
for (const k2 in this.map) {
if (this.map.hasOwnProperty(k2)) {
this.helper.dispose(this.map[k2].value, k2);
}
}
this.map = newMap;
}
resetTouches() {
for (const k in this.map) {
if (this.map.hasOwnProperty(k)) {
this.map[k].touched = false;
}
}
}
retainTouched() {
const newMap = {};
for (const k in this.map) {
if (this.map.hasOwnProperty(k)) {
const en = this.map[k];
if (en.touched) {
newMap[k] = this.map[k];
}
else {
this.helper.dispose(en.value, k);
}
}
}
this.map = newMap;
}
}
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class MultiKeyCacheEntry {
constructor(keyParts, value) {
this.touched = false;
this.keyParts = keyParts;
this.value = value;
}
}
class MultiKeyCache {
constructor(helper) {
this.helper = helper;
this.map = {};
}
combineKeyParts(keyParts) {
const esc = '\\';
const sep = ';';
const escapedEsc = esc + esc;
const escapedSep = esc + sep;
const escapedParts = [];
for (let n = 0; n < keyParts.length; n++) {
escapedParts[n] = keyParts[n].replace(esc, escapedEsc).replace(sep, escapedSep);
}
return escapedParts.join(sep);
}
value(...keyParts) {
const key = this.combineKeyParts(keyParts);
if (!this.map.hasOwnProperty(key)) {
this.map[key] = new MultiKeyCacheEntry(keyParts, this.helper.create(keyParts));
}
const en = this.map[key];
en.touched = true;
return en.value;
}
remove(...keyParts) {
const key = this.combineKeyParts(keyParts);
if (this.map.hasOwnProperty(key)) {
const en = this.map[key];
this.helper.dispose(en.value, en.keyParts);
delete this.map[key];
}
}
retain(...keyParts) {
const newMap = {};
const retainKey = this.combineKeyParts(keyParts);
if (this.map.hasOwnProperty(retainKey)) {
newMap[retainKey] = this.map[retainKey];
delete this.map[retainKey];
}
for (const key in this.map) {
if (this.map.hasOwnProperty(key)) {
const en = this.map[key];
this.helper.dispose(en.value, en.keyParts);
}
}
this.map = newMap;
}
resetTouches() {
for (const key in this.map) {
if (this.map.hasOwnProperty(key)) {
this.map[key].touched = false;
}
}
}
retainTouched() {
const newMap = {};
for (const key in this.map) {
if (this.map.hasOwnProperty(key)) {
const en = this.map[key];
if (en.touched) {
newMap[key] = this.map[key];
}
else {
this.helper.dispose(en.value, en.keyParts);
}
}
}
this.map = newMap;
}
clear() {
for (const key in this.map) {
if (this.map.hasOwnProperty(key)) {
const en = this.map[key];
this.helper.dispose(en.value, en.keyParts);
}
}
this.map = {};
}
}
class TwoKeyCache {
constructor(helper) {
this.cache = new MultiKeyCache({
create: function (keyParts) {
return helper.create(keyParts[0], keyParts[1]);
},
dispose: function (value, keyParts) {
helper.dispose(value, keyParts[0], keyParts[1]);
}
});
}
value(keyPart1, keyPart2) { return this.cache.value(keyPart1, keyPart2); }
remove(keyPart1, keyPart2) { this.cache.remove(keyPart1, keyPart2); }
retain(keyPart1, keyPart2) { this.cache.retain(keyPart1, keyPart2); }
resetTouches() { this.cache.resetTouches(); }
retainTouched() { this.cache.retainTouched(); }
clear() { this.cache.clear(); }
}
class ThreeKeyCache {
constructor(helper) {
this.cache = new MultiKeyCache({
create: function (keyParts) {
return helper.create(keyParts[0], keyParts[1], keyParts[2]);
},
dispose: function (value, keyParts) {
helper.dispose(value, keyParts[0], keyParts[1], keyParts[2]);
}
});
}
value(keyPart1, keyPart2, keyPart3) { return this.cache.value(keyPart1, keyPart2, keyPart3); }
remove(keyPart1, keyPart2, keyPart3) { this.cache.remove(keyPart1, keyPart2, keyPart3); }
retain(keyPart1, keyPart2, keyPart3) { this.cache.retain(keyPart1, keyPart2, keyPart3); }
resetTouches() { this.cache.resetTouches(); }
retainTouched() { this.cache.retainTouched(); }
clear() { this.cache.clear(); }
}
class SixKeyCache {
constructor(helper) {
this.cache = new MultiKeyCache({
create: function (keyParts) {
return helper.create(keyParts[0], keyParts[1], keyParts[2], keyParts[3], keyParts[4], keyParts[5]);
},
dispose: function (value, keyParts) {
helper.dispose(value, keyParts[0], keyParts[1], keyParts[2], keyParts[3], keyParts[4], keyParts[5]);
}
});
}
value(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6) { return this.cache.value(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6); }
remove(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6) { this.cache.remove(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6); }
retain(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6) { this.cache.retain(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6); }
resetTouches() { this.cache.resetTouches(); }
retainTouched() { this.cache.retainTouched(); }
clear() { this.cache.clear(); }
}
class BoundsUnmodifiable {
constructor(bounds) { this.bounds = bounds; }
get iStart() { return this.bounds.iStart; }
get jStart() { return this.bounds.jStart; }
get iEnd() { return this.bounds.iEnd; }
get jEnd() { return this.bounds.jEnd; }
get i() { return this.bounds.i; }
get j() { return this.bounds.j; }
get w() { return this.bounds.w; }
get h() { return this.bounds.h; }
xFrac(i) { return this.bounds.xFrac(i); }
yFrac(j) { return this.bounds.yFrac(j); }
contains(i, j) { return this.bounds.contains(i, j); }
}
class Bounds {
constructor() {
this._iStart = 0;
this._jStart = 0;
this._iEnd = 0;
this._jEnd = 0;
this._unmod = new BoundsUnmodifiable(this);
}
get iStart() { return this._iStart; }
get jStart() { return this._jStart; }
get iEnd() { return this._iEnd; }
get jEnd() { return this._jEnd; }
get i() { return this._iStart; }
get j() { return this._jStart; }
get w() { return this._iEnd - this._iStart; }
get h() { return this._jEnd - this._jStart; }
get unmod() { return this._unmod; }
xFrac(i) {
return (i - this._iStart) / (this._iEnd - this._iStart);
}
yFrac(j) {
return (j - this._jStart) / (this._jEnd - this._jStart);
}
contains(i, j) {
return (this._iStart <= i && i < this._iEnd && this._jStart <= j && j < this._jEnd);
}
setEdges(iStart, iEnd, jStart, jEnd) {
this._iStart = iStart;
this._jStart = jStart;
this._iEnd = iEnd;
this._jEnd = jEnd;
}
setRect(i, j, w, h) {
this.setEdges(i, i + w, j, j + h);
}
setBounds(bounds) {
this.setEdges(bounds.iStart, bounds.iEnd, bounds.jStart, bounds.jEnd);
}
cropToEdges(iCropStart, iCropEnd, jCropStart, jCropEnd) {
this._iStart = clamp$1(iCropStart, iCropEnd, this._iStart);
this._jStart = clamp$1(jCropStart, jCropEnd, this._jStart);
this._iEnd = clamp$1(iCropStart, iCropEnd, this._iEnd);
this._jEnd = clamp$1(jCropStart, jCropEnd, this._jEnd);
}
cropToRect(iCrop, jCrop, wCrop, hCrop) {
this.cropToEdges(iCrop, iCrop + wCrop, jCrop, jCrop + hCrop);
}
cropToBounds(cropBounds) {
this.cropToEdges(cropBounds.iStart, cropBounds.iEnd, cropBounds.jStart, cropBounds.jEnd);
}
}
function newBoundsFromRect(i, j, w, h) {
const b = new Bounds();
b.setRect(i, j, w, h);
return b;
}
function newBoundsFromEdges(iStart, iEnd, jStart, jEnd) {
const b = new Bounds();
b.setEdges(iStart, iEnd, jStart, jEnd);
return b;
}
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class Color {
constructor(r, g, b, a = 1) {
this._r = r;
this._g = g;
this._b = b;
this._a = a;
}
get r() { return this._r; }
get g() { return this._g; }
get b() { return this._b; }
get a() { return this._a; }
get cssString() {
return 'rgba(' + Math.round(255 * this._r) + ',' + Math.round(255 * this._g) + ',' + Math.round(255 * this._b) + ',' + this._a + ')';
}
get rgbaString() {
return '' + Math.round(255 * this._r) + ',' + Math.round(255 * this._g) + ',' + Math.round(255 * this._b) + ',' + Math.round(255 * this._a);
}
withAlphaTimes(aFactor) {
return new Color(this._r, this._g, this._b, aFactor * this._a);
}
}
function darker(color, factor) {
return new Color(color.r * factor, color.g * factor, color.b * factor, color.a);
}
function rgba(r, g, b, a) {
return new Color(r, g, b, a);
}
function rgb(r, g, b) {
return new Color(r, g, b, 1);
}
function sameColor(c1, c2) {
if (c1 === c2) {
return true;
}
if (!hasval(c1) || !hasval(c2)) {
return false;
}
return (c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a);
}
function parseRgba(rgbaString) {
const tokens = rgbaString.split(',', 4);
return new Color(parseInt(tokens[0], 10) / 255, parseInt(tokens[1], 10) / 255, parseInt(tokens[2], 10) / 255, parseInt(tokens[3], 10) / 255);
}
/**
* Creates a Color object based on a CSS color string. Supports the following notations:
* - hex
* - rgb/rgba
* - hsl/hsla
* - named colors
* Behavior is undefined for strings that are not in one of the listed notations.
*
* Optional alphaOverride provided for forcing value of rbga alpha.
*
* Note that different browsers may use different color values for the named colors.
*
*/
let parseCssColor = (function () {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const g = canvas.getContext('2d', { willReadFrequently: true });
return function (cssColorString, alphaOverride) {
g.clearRect(0, 0, 1, 1);
g.fillStyle = cssColorString;
g.fillRect(0, 0, 1, 1);
const rgbaData = g.getImageData(0, 0, 1, 1).data;
const R = rgbaData[0] / 255;
const G = rgbaData[1] / 255;
const B = rgbaData[2] / 255;
const A = hasval(alphaOverride) ? alphaOverride : rgbaData[3] / 255;
return rgba(R, G, B, A);
};
})();
function gray(brightness) {
return new Color(brightness, brightness, brightness, 1);
}
// XXX: Make final
let black = rgb(0, 0, 0);
let white = rgb(1, 1, 1);
let red = rgb(1, 0, 0);
let green = rgb(0, 1, 0);
let blue = rgb(0, 0, 1);
let cyan = rgb(0, 1, 1);
let magenta = rgb(1, 0, 1);
let yellow = rgb(1, 1, 0);
let periwinkle = rgb(0.561, 0.561, 0.961);
// see: http://www.cs.rit.edu/usr/local/pub/wrc/graphics/doc/opengl/books/blue/glOrtho.html
// see: http://www.songho.ca/opengl/gl_projectionmatrix.html#ortho
function glOrtho(left, right, bottom, top, near, far) {
const tx = (right + left) / (right - left);
const ty = (top + bottom) / (top - bottom);
const tz = (far + near) / (far - near);
// GL ES (and therefore WebGL) requires matrices to be column-major
return new Float32Array([
2 / (right - left), 0, 0, 0,
0, 2 / (top - bottom), 0, 0,
0, 0, -2 / (far - near), 0,
-tx, -ty, -tz, 1
]);
}
function glOrthoViewport(viewport) {
return glOrtho(-0.5, viewport.w - 0.5, -0.5, viewport.h - 0.5, -1, 1);
}
function glOrthoAxis(axis) {
return glOrtho(axis.xAxis.vMin, axis.xAxis.vMax, axis.yAxis.vMin, axis.yAxis.vMax, -1, 1);
}
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class Notification {
constructor() {
this._listeners = new OrderedSet([], getObjectId, false);
this._deferring = false;
this._deferred = [];
}
on(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.add(listener); });
}
else {
this._listeners.add(listener);
}
}
off(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.removeValue(listener); });
}
else {
this._listeners.removeValue(listener);
}
}
dispose() {
this._listeners.removeAll();
}
fire() {
this._deferring = true;
try {
for (let n = 0; n < this._listeners.length; n++) {
const consumed = this._listeners.valueAt(n)();
if (consumed) {
return consumed;
}
}
return false;
}
finally {
if (this._deferred.length > 0) {
for (let n = 0; n < this._deferred.length; n++) {
this._deferred[n]();
}
this._deferred = [];
}
this._deferring = false;
}
}
}
class Notification1 {
constructor() {
this._listeners = new OrderedSet([], getObjectId, false);
this._deferring = false;
this._deferred = [];
}
on(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.add(listener); });
}
else {
this._listeners.add(listener);
}
}
off(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.removeValue(listener); });
}
else {
this._listeners.removeValue(listener);
}
}
dispose() {
this._listeners.removeAll();
}
fire(a) {
this._deferring = true;
try {
for (let n = 0; n < this._listeners.length; n++) {
const consumed = this._listeners.valueAt(n)(a);
if (consumed) {
return consumed;
}
}
return false;
}
finally {
if (this._deferred.length > 0) {
for (let n = 0; n < this._deferred.length; n++) {
this._deferred[n]();
}
this._deferred = [];
}
this._deferring = false;
}
}
}
class Notification2 {
constructor() {
this._listeners = new OrderedSet([], getObjectId, false);
this._deferring = false;
this._deferred = [];
}
on(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.add(listener); });
}
else {
this._listeners.add(listener);
}
}
off(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.removeValue(listener); });
}
else {
this._listeners.removeValue(listener);
}
}
dispose() {
this._listeners.removeAll();
}
fire(a, b) {
this._deferring = true;
try {
for (let n = 0; n < this._listeners.length; n++) {
const consumed = this._listeners.valueAt(n)(a, b);
if (consumed) {
return consumed;
}
}
return false;
}
finally {
if (this._deferred.length > 0) {
for (let n = 0; n < this._deferred.length; n++) {
this._deferred[n]();
}
this._deferred = [];
}
this._deferring = false;
}
}
}
class Notification3 {
constructor() {
this._listeners = new OrderedSet([], getObjectId, false);
this._deferring = false;
this._deferred = [];
}
on(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.add(listener); });
}
else {
this._listeners.add(listener);
}
}
off(listener) {
if (this._deferring) {
const self = this;
this._deferred.push(function () { self._listeners.removeValue(listener); });
}
else {
this._listeners.removeValue(listener);
}
}
dispose() {
this._listeners.removeAll();
}
fire(a, b, c) {
this._deferring = true;
try {
for (let n = 0; n < this._listeners.length; n++) {
const consumed = this._listeners.valueAt(n)(a, b, c);
if (consumed) {
return consumed;
}
}
return false;
}
finally {
if (this._deferred.length > 0) {
for (let n = 0; n < this._deferred.length; n++) {
this._deferred[n]();
}
this._deferred = [];
}
this._deferring = false;
}
}
}
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
function requireString(s) {
if (isString(s)) {
return s;
}
else {
throw new Error('Expected a string, but value is ' + s);
}
}
class OrderedSet {
constructor(values = [], idFn = getObjectId, useNotifications = true) {
this._idOf = idFn;
this._valuesArray = copyArray(values);
this._ids = [];
this._indexes = {};
this._valuesMap = {};
for (let n = 0; n < this._valuesArray.length; n++) {
const value = this._valuesArray[n];
const id = requireString(this._idOf(value));
this._ids[n] = id;
this._indexes[id] = n;
this._valuesMap[id] = value;
}
if (useNotifications) {
this._valueAdded = new Notification2();
this._valueMoved = new Notification3();
this._valueRemoved = new Notification2();
}
}
get valueAdded() {
return this._valueAdded;
}
get valueMoved() {
return this._valueMoved;
}
get valueRemoved() {
return this._valueRemoved;
}
get length() {
return this._valuesArray.length;
}
get isEmpty() {
return (this._valuesArray.length === 0);
}
toArray() {
return copyArray(this._valuesArray);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
every(callbackFn, thisArg) {
return this._valuesArray.every(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
some(callbackFn, thisArg) {
return this._valuesArray.some(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
forEach(callbackFn, thisArg) {
this._valuesArray.forEach(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
map(callbackFn, thisArg) {
return this._valuesArray.map(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
filter(callbackFn, thisArg) {
return this._valuesArray.filter(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
reduce(callbackFn, initialValue) {
return this._valuesArray.reduce(callbackFn, initialValue);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedSet is undefined.
*/
reduceRight(callbackFn, initialValue) {
return this._valuesArray.reduceRight(callbackFn, initialValue);
}
idAt(index) {
return this._ids[index];
}
valueAt(index) {
return this._valuesArray[index];
}
indexFor(id) {
return (isString(id) ? this._indexes[id] : undefined);
}
valueFor(id) {
return (isString(id) ? this._valuesMap[id] : undefined);
}
idOf(value) {
return requireString(this._idOf(value));
}
indexOf(value) {
return this._indexes[requireString(this._idOf(value))];
}
hasValue(value) {
return this.hasId(requireString(this._idOf(value)));
}
hasValues(values) {
for (let n = 0; n < values.length; n++) {
if (!this.hasValue(values[n])) {
return false;
}
}
return true;
}
hasId(id) {
return (isString(id) && hasval(this._valuesMap[id]));
}
hasIds(ids) {
for (let n = 0; n < ids.length; n++) {
if (!this.hasId(ids[n])) {
return false;
}
}
return true;
}
add(value, index, moveIfExists) {
index = (hasval(index) ? index : this._valuesArray.length);
if (!hasval(moveIfExists)) {
moveIfExists = false;
}
this._add(value, index, moveIfExists);
}
addAll(values, index, moveIfExists) {
index = (hasval(index) ? index : this._valuesArray.length);
if (!hasval(moveIfExists)) {
moveIfExists = false;
}
for (let n = 0; n < values.length; n++) {
const actualIndex = this._add(values[n], index, moveIfExists);
index = actualIndex + 1;
}
}
_add(value, newIndex, moveIfExists) {
const id = requireString(this._idOf(value));
const oldIndex = this._indexes[id];
if (!hasval(oldIndex)) {
this._ids.splice(newIndex, 0, id);
this._valuesArray.splice(newIndex, 0, value);
this._valuesMap[id] = value;
for (let n = newIndex; n < this._ids.length; n++) {
this._indexes[this._ids[n]] = n;
}
if (this._valueAdded) {
this._valueAdded.fire(value, newIndex);
}
}
else if (newIndex !== oldIndex && moveIfExists) {
this._ids.splice(oldIndex, 1);
this._valuesArray.splice(oldIndex, 1);
if (newIndex > oldIndex) {
this._ids.splice(newIndex, 0, id);
this._valuesArray.splice(newIndex, 0, value);
for (let n = oldIndex; n <= newIndex; n++) {
this._indexes[this._ids[n]] = n;
}
}
else {
this._ids.splice(newIndex, 0, id);
this._valuesArray.splice(newIndex, 0, value);
for (let n = newIndex; n <= oldIndex; n++) {
this._indexes[this._ids[n]] = n;
}
}
if (this._valueMoved) {
this._valueMoved.fire(value, oldIndex, newIndex);
}
}
else {
newIndex = oldIndex;
}
// Return the actual insertion index -- may differ from the originally
// requested index if an existing value had to be moved
return newIndex;
}
removeValue(value) {
this.removeId(requireString(this._idOf(value)));
}
removeId(id) {
if (isString(id)) {
const index = this._indexes[id];
if (hasval(index)) {
this._remove(id, index);
}
}
}
removeIndex(index) {
const id = this._ids[index];
if (isString(id)) {
this._remove(id, index);
}
}
removeAll() {
// Remove from last to first, to minimize index shifting
for (let n = this._valuesArray.length - 1; n >= 0; n--) {
const id = this._ids[n];
this._remove(id, n);
}
}
retainValues(values) {
const idsToRetain = {};
for (let n = 0; n < values.length; n++) {
const id = this._idOf(values[n]);
if (isString(id)) {
idsToRetain[id] = true;
}
}
this._retain(idsToRetain);
}
retainIds(ids) {
const idsToRetain = {};
for (let n = 0; n < ids.length; n++) {
const id = ids[n];
if (isString(id)) {
idsToRetain[id] = true;
}
}
this._retain(idsToRetain);
}
retainIndices(indices) {
const idsToRetain = {};
for (let n = 0; n < indices.length; n++) {
const id = this._ids[indices[n]];
idsToRetain[id] = true;
}
this._retain(idsToRetain);
}
_retain(ids) {
// Remove from last to first, to minimize index shifting
for (let n = this._valuesArray.length - 1; n >= 0; n--) {
const id = this._ids[n];
if (!ids.hasOwnProperty(id)) {
this._remove(id, n);
}
}
}
_remove(id, index) {
const value = this._valuesArray[index];
this._ids.splice(index, 1);
this._valuesArray.splice(index, 1);
delete this._indexes[id];
delete this._valuesMap[id];
for (let n = index; n < this._ids.length; n++) {
this._indexes[this._ids[n]] = n;
}
if (this._valueRemoved) {
this._valueRemoved.fire(value, index);
}
}
}
class OrderedStringSet {
constructor(values = [], useNotifications = true) {
this._valuesArray = [];
this._indexes = {};
for (let n = 0; n < values.length; n++) {
const value = requireString(values[n]);
this._valuesArray[n] = value;
this._indexes[value] = n;
}
if (useNotifications) {
this._valueAdded = new Notification2();
this._valueMoved = new Notification3();
this._valueRemoved = new Notification2();
}
}
get valueAdded() {
return this._valueAdded;
}
get valueMoved() {
return this._valueMoved;
}
get valueRemoved() {
return this._valueRemoved;
}
get length() {
return this._valuesArray.length;
}
get isEmpty() {
return (this._valuesArray.length === 0);
}
toArray() {
return copyArray(this._valuesArray);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
every(callbackFn, thisArg) {
return this._valuesArray.every(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
some(callbackFn, thisArg) {
return this._valuesArray.some(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
forEach(callbackFn, thisArg) {
this._valuesArray.forEach(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
map(callbackFn, thisArg) {
return this._valuesArray.map(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
filter(callbackFn, thisArg) {
return this._valuesArray.filter(callbackFn, thisArg);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
reduce(callbackFn, initialValue) {
return this._valuesArray.reduce(callbackFn, initialValue);
}
/**
* The callback should not modify its array arg; if it does, the subsequent behavior
* of this OrderedStringSet is undefined.
*/
reduceRight(callbackFn, initialValue) {
return this._valuesArray.reduceRight(callbackFn, initialValue);
}
valueAt(index) {
return this._valuesArray[index];
}
indexOf(value) {
return (isString(value) ? this._indexes[value] : undefined);
}
hasValue(value) {
return (isString(value) && hasval(this._indexes[value]));
}
hasValues(values) {
for (let n = 0; n < values.length; n++) {
if (!this.hasValue(values[n])) {
return false;
}
}
return true;
}
add(value, index, moveIfExists) {
index = (hasval(index) ? index : this._valuesArray.length);
if (!hasval(moveIfExists)) {
moveIfExists = false;
}
this._add(value, index, moveIfExists);
}
addAll(values, index, moveIfExists) {
index = (hasval(index) ? index : this._valuesArray.length);
if (!hasval(moveIfExists)) {
moveIfExists = false;
}
for (let n = 0; n < values.length; n++) {
const actualIndex = this._add(values[n], index, moveIfExists);
index = actualIndex + 1;
}
}
_add(value, newIndex, moveIfExists) {
requireString(value);
const oldIndex = this._indexes[value];
if (!hasval(oldIndex)) {
this._valuesArray.splice(newIndex, 0, value);
for (let n = newIndex; n < this._valuesArray.length; n++) {
this._indexes[this._valuesArray[n]] = n;
}
if (this._valueAdded) {
this._valueAdded.fire(value, newIndex);
}
}
else if (newIndex !== oldIndex && moveIfExists) {
this._valuesArray.splice(oldIndex, 1);
if (newIndex > oldIndex) {
this._valuesArray.splice(newIndex, 0, value);
for (let n = oldIndex; n <= newIndex; n++) {
this._indexes[this._valuesArray[n]] = n;
}
}
else {
this._valuesArray.splice(newIndex, 0, value);
for (let n = newIndex; n <= oldIndex; n++) {
this._indexes[this._valuesArray[n]] = n;
}
}
if (this._valueMoved) {
this._valueMoved.fire(value, oldIndex, newIndex);
}
}
else {
newIndex = oldIndex;
}
// Return the actual insertion index -- may differ from the originally
// requested index if an existing value had to be moved
return newIndex;
}
removeValue(value) {
if (isString(value)) {
const index = this._indexes[value];
if (hasval(index)) {
this._remove(value, index);
}
}
}
removeIndex(index) {
const value = this._valuesArray[index];
if (isString(value)) {
this._remove(value, index);
}
}
removeAll() {
// Remove from last to first, to minimize index shifting
for (let n = this._valuesArray.length - 1; n >= 0; n--) {
const value = this._valuesArray[n];
this._remove(value, n);
}
}
retainValues(values) {
const valuesToRetain = {};
for (let n = 0; n < values.length; n++) {
const value = values[n];
if (isString(value)) {
valuesToRetain[value] = true;
}
}
this._retain(valuesToRetain);
}
retainIndices(indices) {
const valuesToRetain = {};
for (let n = 0; n < indices.length; n++) {
const value = this._valuesArray[indices[n]];
valuesToRetain[value] = true;
}
this._retain(valuesToRetain);
}
_retain(values) {
// Remove from last to first, to minimize index shifting
for (let n = this._valuesArray.length - 1; n >= 0; n--) {
const value = this._valuesArray[n];
if (!values.hasOwnProperty(value)) {
this._remove(value, n);
}
}
}
_remove(value, index) {
this._valuesArray.splice(index, 1);
delete this._indexes[value];
for (let n = index; n < this._valuesArray.length; n++) {
this._indexes[this._valuesArray[n]] = n;
}
if (this._valueRemoved) {
this._valueRemoved.fire(value, index);
}
}
}
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code mus