cables_dev/cables/src/core/utils.js
/**
* @namespace external:CABLES#Utils
*/
import { CONSTANTS } from "./constants.js";
const UTILS = {};
/**
* Merge two Float32Arrays.
* @function float32Concat
* @memberof Utils
* @param {Float32Array} first Left-hand side array
* @param {Float32Array} second Right-hand side array
* @return {Float32Array}
* @static
*/
UTILS.float32Concat = function (first, second)
{
if (!(first instanceof Float32Array)) first = new Float32Array(first);
if (!(second instanceof Float32Array)) second = new Float32Array(second);
const result = new Float32Array(first.length + second.length);
result.set(first);
result.set(second, first.length);
return result;
};
/**
* get op shortname: only last part of fullname and without version
* @function getShortOpName
* @memberof CABLES
* @param {string} fullname full op name
* @static
*/
export const getShortOpName = function (fullname)
{
let name = fullname.split(".")[fullname.split(".").length - 1];
if (name.contains(CONSTANTS.OP.OP_VERSION_PREFIX))
{
const n = name.split(CONSTANTS.OP.OP_VERSION_PREFIX)[1];
name = name.substring(0, name.length - (CONSTANTS.OP.OP_VERSION_PREFIX + n).length);
}
return name;
};
/**
* randomize order of an array
* @function shuffleArray
* @memberof Utils
* @param {Array|Float32Array} array {Array} original
* @return {Array|Float32Array} shuffled array
* @static
*/
export const shuffleArray = function (array)
{
for (let i = array.length - 1; i > 0; i--)
{
const j = Math.floor(Math.seededRandom() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
/**
* generate a short "relativly unique" id
* @function shortId
* @memberof Utils
* @return {String} generated ID
* @static
*/
const _shortIds = {};
const _shortId = function ()
{
let str = Math.random().toString(36).substr(2, 9);
if (_shortIds.hasOwnProperty(str)) str = _shortId();
_shortIds[str] = true;
return str;
};
export const shortId = _shortId;
/**
* generate a UUID
* @function uuid
* @memberof Utils
* @return {String} generated UUID
* @static
*/
const _uuid = function ()
{
let d = new Date().getTime();
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) =>
{
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
});
return uuid;
};
export const uuid = _uuid;
export const generateUUID = _uuid;
export function cleanJson(obj)
{
for (const i in obj)
{
if (obj[i] && typeof objValue === "object" && obj[i].constructor === Object) obj[i] = cleanJson(obj[i]);
if (obj[i] === null || obj[i] === undefined) delete obj[i];
else if (Array.isArray(obj[i]) && obj[i].length == 0) delete obj[i];
}
return obj;
}
/**
* @see http://stackoverflow.com/q/7616461/940217
* @memberof Utils
* @param str
* @param prefix
* @return {string}
*/
const _prefixedHash = function (str, prefix = "id")
{
let hash = 0;
if (Array.prototype.reduce)
{
hash = str.split("").reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
}
else
{
if (str.length > 0)
{
for (let i = 0; i < str.length; i++)
{
let character = str.charCodeAt(i);
hash = ((hash << 5) - hash) + character;
hash &= hash; // Convert to 32bit integer
}
}
}
return prefix + "" + hash;
};
export const prefixedHash = _prefixedHash;
/**
* generate a simple ID
* @function simpleId
* @memberof Utils
* @return {Number} new id
* @static
*/
let simpleIdCounter = 0;
export const simpleId = function ()
{
simpleIdCounter++;
return simpleIdCounter;
};
/**
* smoothStep a value
* @function smoothStep
* @memberof Utils
* @function
* @param {Number} perc value value to be smoothed [0-1]
* @return {Number} smoothed value
* @static
*/
export const smoothStep = function (perc)
{
const x = Math.max(0, Math.min(1, (perc - 0) / (1 - 0)));
perc = x * x * (3 - 2 * x); // smoothstep
return perc;
};
/**
* smootherstep a value
* @function smootherStep
* @memberof Utils
* @param {Number} perc value to be smoothed [0-1]
* @return {Number} smoothed value
* @static
*/
export const smootherStep = function (perc)
{
const x = Math.max(0, Math.min(1, (perc - 0) / (1 - 0)));
perc = x * x * x * (x * (x * 6 - 15) + 10); // smootherstep
return perc;
};
/**
* clamp number / make sure its between min/max
* @function clamp
* @memberof Utils
* @param {Number} value value to be mapped
* @param {Number} min minimum value
* @param {Number} max maximum value
* @static
*/
export const clamp = function (value, min, max)
{
return Math.min(Math.max(value, min), max);
};
/**
* map a value in a range to a value in another range
* @function map
* @memberof Utils
* @param {Number} x value to be mapped
* @param {Number} _oldMin old range minimum value
* @param {Number} _oldMax old range maximum value
* @param {Number} _newMin new range minimum value
* @param {Number} _newMax new range maximum value
* @param {Number} _easing
* @return {Number} mapped value
* @static
*/
export const map = function (x, _oldMin, _oldMax, _newMin, _newMax, _easing)
{
if (x >= _oldMax) return _newMax;
if (x <= _oldMin) return _newMin;
let reverseInput = false;
const oldMin = Math.min(_oldMin, _oldMax);
const oldMax = Math.max(_oldMin, _oldMax);
if (oldMin != _oldMin) reverseInput = true;
let reverseOutput = false;
const newMin = Math.min(_newMin, _newMax);
const newMax = Math.max(_newMin, _newMax);
if (newMin != _newMin) reverseOutput = true;
let portion = 0;
let r = 0;
if (reverseInput) portion = ((oldMax - x) * (newMax - newMin)) / (oldMax - oldMin);
else portion = ((x - oldMin) * (newMax - newMin)) / (oldMax - oldMin);
if (reverseOutput) r = newMax - portion;
else r = portion + newMin;
if (!_easing) return r;
if (_easing == 1)
{
// smoothstep
x = Math.max(0, Math.min(1, (r - _newMin) / (_newMax - _newMin)));
return _newMin + x * x * (3 - 2 * x) * (_newMax - _newMin);
}
if (_easing == 2)
{
// smootherstep
x = Math.max(0, Math.min(1, (r - _newMin) / (_newMax - _newMin)));
return _newMin + x * x * x * (x * (x * 6 - 15) + 10) * (_newMax - _newMin);
}
return r;
};
/**
* @namespace Math
*/
/**
* set random seed for seededRandom()
* @memberof Math
* @type Number
* @static
*/
Math.randomSeed = 1;
Math.setRandomSeed = function (seed)
{
// https://github.com/cables-gl/cables_docs/issues/622
Math.randomSeed = seed * 50728129;
if (seed != 0)
{
Math.randomSeed = Math.seededRandom() * 17624813;
Math.randomSeed = Math.seededRandom() * 9737333;
}
};
/**
* generate a seeded random number
* @function seededRandom
* @memberof Math
* @param {Number} max minimum possible random number
* @param {Number} min maximum possible random number
* @return {Number} random value
* @static
*/
Math.seededRandom = function (max, min)
{
if (Math.randomSeed === 0) Math.randomSeed = Math.random() * 999;
max = max || 1;
min = min || 0;
Math.randomSeed = (Math.randomSeed * 9301 + 49297) % 233280;
const rnd = Math.randomSeed / 233280.0;
return min + rnd * (max - min);
};
// ----------------------------------------------------------------
/**
* returns true if parameter is a number
* @function isNumeric
* @memberof Utils
* @param {Any} n value The value to check.
* @return {Boolean}
* @static
*/
UTILS.isNumeric = function (n)
{
return !isNaN(parseFloat(n)) && isFinite(n);
};
/**
* returns true if parameter is array
* @function isArray
* @param {Any} v value Value to check
* @memberof Utils
* @return {Boolean}
* @static
*/
UTILS.isArray = function (v)
{
return Object.prototype.toString.call(v) === "[object Array]";
};
/**
* @namespace String
*/
/**
* append a linebreak to a string
* @function endl
* @memberof String
* @return {String} string with newline break appended ('\n')
*/
String.prototype.endl = function ()
{
return this + "\n";
};
/**
* return true if string starts with prefix
* @function startsWith
* @memberof String
* @param {String} prefix The prefix to check.
* @return {Boolean}
*/
String.prototype.startsWith = function (prefix)
{
if (!this || !prefix) return false;
if (this.length >= prefix.length)
{
if (this.substring(0, prefix.length) == prefix) return true;
}
return false;
// return this.indexOf(prefix) === 0;
};
/**
* return true if string ends with suffix
* @function endsWith
* @memberof String
* @param {String} suffix
* @return {Boolean}
*/
String.prototype.endsWith = String.prototype.endsWith || function (suffix)
{
return this.match(suffix + "$") == suffix;
};
/**
* return true if string contains string
* @function contains
* @memberof String
* @param {String} searchStr
* @return {Boolean}
*/
String.prototype.contains = String.prototype.contains || function (searchStr)
{
return this.indexOf(searchStr) > -1;
};
// ----------------------------------------------------------------
/**
* append a unique/random parameter to a url, so the browser is forced to reload the file, even if its cached
* @function cacheBust
* @static
* @memberof Utils
* @param {String} url The url to append the cachebuster parameter to.
* @return {String} url with cachebuster parameter
*/
export const cacheBust = function (url = "")
{
if (!url) return "";
if (url.startsWith("data:")) return;
if (url.contains("?")) url += "&";
else url += "?";
return url + "cache=" + CABLES.uuid();
};
/**
* copy the content of an array
* @function copyArray
* @static
* @memberof Utils
* @param {Array} src sourceArray
* @param {Array} dst optional
* @return {Array} dst
*/
export const copyArray = function (src, dst)
{
if (!src) return null;
dst = dst || [];
dst.length = src.length;
for (let i = 0; i < src.length; i++)
{
dst[i] = src[i];
}
return dst;
};
/**
* return the filename part of a url without extension
* @function basename
* @static
* @memberof Utils
* @param {String} url
* @return {String} just the filename
*/
export const basename = function (url)
{
let name = CABLES.filename(url);
const parts2 = name.split(".");
name = parts2[0];
return name;
};
/**
* output a stacktrace to the console
* @function logStack
* @static
* @memberof Utils
*/
export const logStack = function ()
{
console.log("logstack", (new Error()).stack);
};
/**
* return the filename part of a url
* @function filename
* @static
* @memberof Utils
* @param {String} url
* @return {String} just the filename
*/
export const filename = function (url)
{
let name = "";
if (!url) return "";
if (url.startsWith("data:") && url.contains(":"))
{
const parts = url.split(",");
return parts[0];
}
let parts = (url + "").split("/");
if (parts.length > 0)
{
const str = parts[parts.length - 1];
let parts2 = str.split("?");
name = parts2[0];
}
return name || "";
};
export const ajaxSync = function (url, cb, method, post, contenttype)
{
request({
"url": url,
"cb": cb,
"method": method,
"data": post,
"contenttype": contenttype,
"sync": true,
});
};
/**
* make an ajax request
* @static
* @function ajax
* @param url
* @param cb
* @param method
* @param post
* @param contenttype
* @param jsonP
* @param headers
* @param options
*/
export const ajax = function (url, cb, method, post, contenttype, jsonP, headers = {}, options = {})
{
const requestOptions = {
"url": url,
"cb": cb,
"method": method,
"data": post,
"contenttype": contenttype,
"sync": false,
"jsonP": jsonP,
"headers": headers,
};
if (options && options.credentials) requestOptions.credentials = options.credentials;
request(requestOptions);
};
export const request = function (options)
{
if (!options.hasOwnProperty("asynch")) options.asynch = true;
let xhr;
try
{
xhr = new XMLHttpRequest();
}
catch (e) {}
xhr.onreadystatechange = function ()
{
if (xhr.readyState != 4) return;
if (options.cb)
{
if (xhr.status == 200 || xhr.status == 0) options.cb(false, xhr.responseText, xhr);
else options.cb(true, xhr.responseText, xhr);
}
};
try
{
xhr.open(options.method ? options.method.toUpperCase() : "GET", options.url, !options.sync);
}
catch (e)
{
if (options.cb && e) options.cb(true, e.msg, xhr);
}
if (typeof options.headers === "object")
{
if (options.headers)
{
const keys = Object.keys(options.headers);
for (let i = 0; i < keys.length; i++)
{
const name = keys[i];
const value = options.headers[name];
xhr.setRequestHeader(name, value);
}
}
}
if (options.credentials && options.credentials !== "omit")
{
xhr.withCredentials = true;
}
try
{
if (!options.post && !options.data)
{
xhr.send();
}
else
{
xhr.setRequestHeader(
"Content-type",
options.contenttype ? options.contenttype : "application/x-www-form-urlencoded",
);
xhr.send(options.data || options.post);
}
}
catch (e)
{
if (options.cb) options.cb(true, e.msg, xhr);
}
};
export const keyCodeToName = function (keyCode)
{
if (!keyCode && keyCode !== 0) return "Unidentified";
const keys = {
"8": "Backspace",
"9": "Tab",
"12": "Clear",
"13": "Enter",
"16": "Shift",
"17": "Control",
"18": "Alt",
"19": "Pause",
"20": "CapsLock",
"27": "Escape",
"32": "Space",
"33": "PageUp",
"34": "PageDown",
"35": "End",
"36": "Home",
"37": "ArrowLeft",
"38": "ArrowUp",
"39": "ArrowRight",
"40": "ArrowDown",
"45": "Insert",
"46": "Delete",
"112": "F1",
"113": "F2",
"114": "F3",
"115": "F4",
"116": "F5",
"117": "F6",
"118": "F7",
"119": "F8",
"120": "F9",
"121": "F10",
"122": "F11",
"123": "F12",
"144": "NumLock",
"145": "ScrollLock",
"224": "Meta"
};
if (keys[keyCode])
{
return keys[keyCode];
}
else
{
return String.fromCharCode(keyCode);
}
};
// ----------------------------------------------------------------
window.performance = window.performance || {
"offset": Date.now(),
"now": function now()
{
return Date.now() - this.offset;
},
};
export const logErrorConsole = function (initiator)
{
CABLES.errorConsole = CABLES.errorConsole || { "log": [] };
CABLES.errorConsole.log.push({ "initiator": initiator, "arguments": arguments });
if (!CABLES.errorConsole.ele)
{
const ele = document.createElement("div");
ele.id = "cablesErrorConsole";
ele.style.width = "90%";
ele.style.height = "300px";
ele.style.zIndex = "9999999";
ele.style.display = "inline-block";
ele.style.position = "absolute";
ele.style.padding = "10px";
ele.style.fontFamily = "monospace";
ele.style.color = "red";
ele.style.backgroundColor = "#200";
CABLES.errorConsole.ele = ele;
document.body.appendChild(ele);
}
let logHtml = "ERROR<br/>for more info, open your browsers dev tools console (Ctrl+Shift+I or Command+Alt+I)<br/>";
for (let l = 0; l < CABLES.errorConsole.log.length; l++)
{
logHtml += CABLES.errorConsole.log[l].initiator + " ";
for (let i = 1; i < CABLES.errorConsole.log[l].arguments.length; i++)
{
if (i > 2)logHtml += ", ";
let arg = CABLES.errorConsole.log[l].arguments[i];
if (arg.constructor.name.indexOf("Error") > -1 || arg.constructor.name.indexOf("error") > -1)
{
let txt = "Uncaught ErrorEvent ";
if (arg.message)txt += " message: " + arg.message;
logHtml += txt;
}
else if (typeof arg == "string")
logHtml += arg;
else if (typeof arg == "number")
logHtml += String(arg) + " ";
}
logHtml += "<br/>";
}
CABLES.errorConsole.ele.innerHTML = logHtml;
};
export { UTILS };