/* It is EMCA6 but without a lot of builtin objects and various functions. There are no: * Promises and so on (Generators, async) * WeakMaps and so on (weakset, weakref) * Typed arrays * Proxys * Modules * Symbols (use closures) In addition to the removal of a bunch of stuff as seen here. Access prototypes through __proto__ instead of the long-winded Object.getProtoTypeOf. */ /* Object.getPrototypeOf = undefined; Object.setPrototypeOf = undefined; Reflect = undefined Symbol = undefined; URIError = undefined; Proxy = undefined; Map = undefined; WeakMap = undefined; Promise = undefined; Set = undefined; WeakSet = undefined; */ Number.roman = { M: 1000, D: 500, C: 100, L: 50, X: 10, V: 5, I: 1 }; var convert = {}; convert.romanize = function(num) { if (!+num) return false; var digits = String(+num).split(''); var key = ['','C','CC','CCC','CD','D','DC','DCC','DCCC','CM', '','X','XX','XXX','XL','L','LX','LXX','LXXX','XC', '','I','II','III','IV','V','VI','VII','VIII','IX']; var roman = '', i = 3; while (i--) roman = (key[+digits.pop() + (i * 10)] || '') + roman; return Array(+digits.join('') + 1).join('M') + roman; } convert.deromanize = function(str) { var str = str.toUpperCase(); var validator = /^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/; var token = /[MDLV]|C[MD]?|X[CL]?|I[XV]?/g; var key = {M:1000,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1}; var num = 0, m; if (!(str && validator.test(str))) return false; while (m = token.exec(str)) num += key[m[0]]; return num; } convert.buf2hex = function(buffer) { // buffer is an ArrayBuffer return [...new Uint8Array(buffer)] .map(x => x.toString(16).padStart(2, '0')) .join(' '); } /* Time values are always expressed in terms of real earth-seconds */ Object.assign(time, { hour2minute() { return this.hour/this.minute; }, day2hour() { return this.day/this.hour; }, minute2second() { return this.minute/this.second; }, week2day() { return this.week/this.day; }, }); time.strparse = { yyyy: "year", mm: "month", m: "month", eee: "yday", dd: "day", d: "day", v: "weekday", hh: "hour", h: "hour", nn: "minute", n: "minute", ss: "second", s: "second", }; time.doc = { doc: "Functions for manipulating time.", second: "Earth-seconds in a second.", minute: "Seconds in a minute.", hour: "Seconds in an hour.", day: "Seconds in a day.", week: "Seconds in a week.", weekdays: "Names of the days of the week.", monthstr: "Full names of the months of the year.", epoch: "Times are expressed in terms of day 0 at hms 0 of this year.", now: "Get the time now.", computer_zone: "Get the time zone of the running computer.", computer_dst: "Return true if the computer is in daylight savings.", yearsize: "Given a year, return the number of days in that year.", monthdays: "Number of days in each month.", fmt: "Default format for time.", record: "Given a time, return an object with time fields.", number: "Return the number representation of a given time.", text: "Return a text formatted time." }; time.second = 1; time.minute = 60; time.hour = 3_600; time.day = 86_400; time.week = 604_800; time.weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; time.monthstr = ["January", "February", "March", 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; time.epoch = 1970; time.isleap = function(year) { return this.yearsize(year) === 366; } time.isleap.doc = "Return true if the given year is a leapyear."; time.yearsize = function(y) { if (y%4 === 0 && (y%100 != 0 || y%400 === 0)) return 366; return 365; } time.timecode = function(t, fps = 24) { var s = Math.trunc(t); t -= s; return `${s}:${Math.trunc(fps*s)}`; } time.monthdays = [31,28,31,30,31,30,31,31,30,31,30,31]; time.zones = {}; time.zones['-12'] = 'IDLW'; time.record = function(num, zone = this.computer_zone()) { if (typeof num === 'object') return num; else if (typeof num === 'number') { var monthdays = this.monthdays.slice(); var rec = { second: 0, minute: 0, hour: 0, yday: 0, year: 0, zone: 0 }; rec.zone = zone; num += zone*this.hour; var hms = num % this.day; var day = parseInt(num/this.day); if (hms < 0) { hms += this.day; day--; } rec.second = hms%this.minute; var d1 = Math.floor(hms/this.minute); rec.minute = d1 % this.minute; rec.hour = Math.floor(d1/this.minute); /* addend%7 is 4 */ rec.weekday = (day + 4503599627370496+2)%7; var d1 = this.epoch; if (day >= 0) for (d1= this.epoch; day >= this.yearsize(d1); d1++) day -= this.yearsize(d1); else for (d1 = this.epoch; day < 0; d1--) day += this.yearsize(d1-1); rec.year = d1; if (rec.year <= 0) rec.ce = 'BC'; else rec.ce = 'AD'; rec.yday = day; if (this.yearsize(d1) === 366) monthdays[1] = 29; var d0 = day; for (d1 = 0; d0 >= monthdays[d1]; d1++) d0 -= monthdays[d1]; monthdays[1] = 28; rec.day = d0 + 1; rec.month = d1; return rec; } } time.number = function(rec) { if (typeof rec === 'number') return rec; else if (typeof rec === 'object') { var c = 0; var year = rec.year ? rec.year : 0; var hour = rec.hour ? rec.hour : 0; var minute = rec.minute ? rec.minute : 0; var second = rec.second ? rec.second : 0; var zone = rec.zone ? rec.zone : 0; if (year > this.epoch) for (var i = this.epoch; i < year; i++) c += this.day*this.yearsize(i); else if (year < this.epoch) { for (var i = this.epoch-1; i > year; i--) c += this.day*this.yearsize(i); c += (this.yearsize(year)-yday-1)*this.day; c += (this.day2hour()-hour-1)*this.hour; c += (this.hour2minute()-minute-1)*this.minute; c += (this.minute2second()-second); c += zone*this.hour; c *= -1; return c; } c += second; c += minute * this.minute; c += hour * this.hour; c += yday * this.day; c -= zone * this.hour; return c; } } /* Time formatting yyyy - year in a 4 digit field y - as many digits as necessary mm - month (1-12) mB - month name mb - abbreviated month name dd - day (1-31) d c - if the year is <= 0, BC. Otherwise, AD. hh - hour h nn - minutes n ss - seconds s v - day of the week (0-6) vB - weekday name vb - abbreviated weekday name a - am/pm z - zone, -12 to +11 */ time.fmt = "vB mB d h:nn:ss TZz a y c"; /* If num is a number, converts to a rec first. */ time.text = function(num, fmt = this.fmt, zone) { var rec = num; if (typeof rec === 'number') rec = time.record(num, zone); zone = rec.zone; if (fmt.match('a')) { if (rec.hour >= 13) { rec.hour -= 12; fmt = fmt.replaceAll('a', 'PM'); } else if (rec.hour === 12) fmt = fmt.replaceAll('a', 'PM'); else if (rec.hour === 0) { rec.hour = 12; fmt = fmt.replaceAll('a', 'AM'); } else fmt = fmt.replaceAll('a', 'AM'); } var year = rec.year > 0 ? rec.year : rec.year - 1; if (fmt.match('c')) { if (year < 0) { year = Math.abs(year); fmt = fmt.replaceAll('c', 'BC'); } else fmt = fmt.replaceAll('c', 'AD'); } fmt = fmt.replaceAll('yyyy', year.toString().padStart(4,'0')); fmt = fmt.replaceAll('y', year); fmt = fmt.replaceAll('eee', rec.yday+1); fmt = fmt.replaceAll('dd', rec.day.toString().padStart(2,'0')); fmt = fmt.replaceAll('d', rec.day); fmt = fmt.replaceAll('hh', rec.hour.toString().padStart(2,'0')); fmt = fmt.replaceAll('h', rec.hour); fmt = fmt.replaceAll('nn', rec.minute.toString().padStart(2,'0')); fmt = fmt.replaceAll('n', rec.minute); fmt = fmt.replaceAll('ss', rec.second.toString().padStart(2, '0')); fmt = fmt.replaceAll('s', rec.second); fmt = fmt.replaceAll('z', zone >= 0 ? "+" + zone : zone); fmt = fmt.replaceAll(/mm[^bB]/g, rec.month+1); fmt = fmt.replaceAll(/m[^bB]/g, rec.month+1); fmt = fmt.replaceAll(/v[^bB]/g, rec.weekday); fmt = fmt.replaceAll('mb', this.monthstr[rec.month].slice(0,3)); fmt = fmt.replaceAll('mB', this.monthstr[rec.month]); fmt = fmt.replaceAll('vB', this.weekdays[rec.weekday]); fmt = fmt.replaceAll('vb', this.weekdays[rec.weekday].slice(0,3)); return fmt; } Object.methods = function(o) { var m = []; Object.keys(o).forEach(function(k) { if (typeof o[k] === 'function') m.push(k); }); return m; } Object.methods.doc = "Retun an array of all functions an object has access to."; Object.dig = function(obj, path, def = {}) { var pp = path.split('.'); for (var i = 0; i < pp.length-1; i++) { obj = obj[pp[i]] = obj[pp[i]] || {}; } obj[pp[pp.length-1]] = def; return def; } Object.rkeys = function(o) { var keys = []; Object.keys(o).forEach(function(key) { keys.push(key); if (Object.isObject(o[key])) keys.push(Object.rkeys(o[key])); }); return keys; } Object.readonly = function(o, name, msg) { var tmp = {}; var prop = Object.getOwnPropertyDescriptor(o, name); if (!prop) { console.error(`Attempted to make property ${name} readonly, but it doesn't exist on ${o}.`); return; } Object.defineProperty(tmp, name, prop); prop.get = function() { return tmp[name]; } prop.set = function() { console.warn(`Attempted to set readonly property ${name}`); } Object.defineProperty(o, name, prop); } Object.mixin = function(target, source) { if (typeof source !== 'object') return target; Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); return target; }; Object.mix = function(...objs) { var n = {}; for (var o of objs) Object.mixin(n,o); return n; } Object.deepmixin = function(target, source) { var o = source; while (o !== Object.prototype) { Object.mixin(target, o); o = o.__proto__; }; } Object.deepfreeze = function(obj) { for (var key in obj) { if (typeof obj[key] === 'object') Object.deepfreeze(obj[key]); } Object.freeze(obj); } /* Goes through each key and overwrites if it's present */ Object.dainty_assign = function(target, source) { Object.keys(source).forEach(function(k) { if (typeof source[k] === 'function') return; if (!(k in target)) return; if (Array.isArray(source[k])) target[k] = deep_copy(source[k]); else if (Object.isObject(source[k])) Object.dainty_assign(target[k], source[k]); else target[k] = source[k]; }); } Object.isObject = function(o) { return (o instanceof Object && !(o instanceof Array)); } Object.setter_assign = function(target, source) { for (var key in target) if (Object.isAccessor(target,key) && typeof source[key] !== 'undefined') target[key] = source[key]; } Object.containingKey = function(obj, prop) { if (typeof obj !== 'object') return undefined; if (!(prop in obj)) return undefined; var o = obj; while (o.__proto__ && !Object.hasOwn(o, prop)) o = o.__proto__; return o; } Object.access = function(obj, name) { var dig = name.split('.'); for (var i of dig) { obj = obj[i]; if (!obj) return undefined; } return obj; } Object.isAccessor = function(obj, prop) { var o = Object.containingKey(obj,prop); if (!o) return false; var desc = Object.getOwnPropertyDescriptor(o,prop); if (!desc) return false; if (desc.get || desc.set) return true; return false; } Object.mergekey = function(o1,o2,k) { if (!o2) return; if (typeof o2[k] === 'object') { if (Array.isArray(o2[k])) o1[k] = deep_copy(o2[k]); else { if (!o1[k]) o1[k] = {}; if (typeof o1[k] === 'object') Object.merge(o1[k], o2[k]); else o1[k] = o2[k]; } } else o1[k] = o2[k]; } /* Same as merge from Ruby */ /* Adds objs key by key to target */ Object.merge = function(target, ...objs) { for (var obj of objs) for (var key of Object.keys(obj)) Object.mergekey(target,obj,key); return target; } Object.totalmerge = function(target, ...objs) { for (var obj of objs) for (var key in obj) Object.mergekey(target,obj,key); return target; } /* Returns a new object with undefined, null, and empty values removed. */ Object.compact = function(obj) { } Object.totalassign = function(to, from) { for (var key in from) to[key] = from[key]; } /* Prototypes out an object and assigns values */ Object.copy = function(proto, ...objs) { var c = Object.create(proto); for (var obj of objs) Object.mixin(c, obj); return c; } /* OBJECT DEFININTioNS */ Object.defHidden = function(obj, prop) { Object.defineProperty(obj, prop, {enumerable:false, writable:true}); } Object.hide = function(obj,...props) { for (var prop of props) { var p = Object.getOwnPropertyDescriptor(obj,prop); if (!p) { console.error(`No property of name ${prop}.`); continue; } p.enumerable = false; Object.defineProperty(obj, prop, p); } } Object.unhide = function(obj, ...props) { for (var prop of props) { var p = Object.getOwnPropertyDescriptor(obj,prop); if (!p) continue; p.enumerable = true; Object.defineProperty(obj, prop, p); } } Object.defineProperty(Object.prototype, 'obscure', { value: function(name) { Object.defineProperty(this, name, { enumerable: false }); } }); Object.defineProperty(Object.prototype, 'mixin', { value: function(obj) { if (typeof obj === 'string') obj = use(obj, this); if (obj) Object.mixin(this, obj); }, }); Object.defineProperty(Object.prototype, 'hasOwn', { value: function(x) { return this.hasOwnProperty(x); } }); Object.defineProperty(Object.prototype, 'defn', { value: function(name, val) { Object.defineProperty(this, name, { value:val, writable:true, configurable:true }); } }); Object.defineProperty(Object.prototype, 'nulldef', { value: function(name, val) { if (!this.hasOwnProperty(name)) this[name] = val; } }); Object.defineProperty(Object.prototype, 'prop_obj', { value: function() { return JSON.parse(JSON.stringify(this)); } }); /* defc 'define constant'. Defines a value that is not writable. */ Object.defineProperty(Object.prototype, 'defc', { value: function(name, val) { Object.defineProperty(this,name, { value: val, writable:false, enumerable:true, configurable:false, }); } }); Object.defineProperty(Object.prototype, 'stick', { value: function(prop) { Object.defineProperty(this, prop, {writable:false}); } }); Object.defineProperty(Object.prototype, 'harden', { value: function(prop) { Object.defineProperty(this, prop, {writable:false, configurable:false, enumerable: false}); } }); Object.defineProperty(Object.prototype, 'deflock', { value: function(prop) { Object.defineProperty(this,prop, {configurable:false}); } }); Object.defineProperty(Object.prototype, 'forEach', { value: function(fn) { Object.values(this).forEach(fn); } }); Object.defineProperty(Object.prototype, 'map', { value: function(fn) { var a = []; Object.values(this).forEach(function(x) { a.push(fn(x)); }); return a; } }); Object.empty = function(obj) { return Object.keys(obj).length === 0; } Object.defineProperty(Object.prototype, 'nth', { value: function(x) { if (this.empty || x >= Object.keys(this).length) return null; return this[Object.keys(this)[x]]; }, }); Object.defineProperty(Object.prototype, 'filter', { value: function(fn) { return Object.values(this).filter(fn); } }); Object.defineProperty(Object.prototype, 'push', { value: function(val) { var str = val.toString(); str = str.replaceAll('.', '_'); var n = 1; var t = str; while (Object.hasOwn(this,t)) { t = str + n; n++; } this[t] = val; return t; } }); Object.defineProperty(Object.prototype, 'findIndex', { value: function(x) { var i = 0; for (var key in this) { if (this[key] === x) return i; i++; } return -1; } }); /* STRING DEFS */ Object.defineProperty(String.prototype, 'next', { value: function(char, from) { if (!Array.isArray(char)) char = [char]; if (from > this.length-1) return -1; else if (!from) from = 0; var find = this.slice(from).search(char[0]); if (find === -1) return -1; else return from + find; var i = 0; var c = this.charAt(from+i); while (!char.includes(c)) { i++; if (from+i >this.length-1) return -1; c = this.charAt(from+i); } return from+i; } }); Object.defineProperty(String.prototype, 'prev', { value: function(char, from, count) { if (from > this.length-1) return -1; else if (!from) from = this.length-1; if (!count) count = 0; var find = this.slice(0,from).lastIndexOf(char); while (count > 1) { find = this.slice(0,find).lastIndexOf(char); count--; } if (find === -1) return 0; else return find; } }); Object.defineProperty(String.prototype, 'shift', { value: function(n) { if (n === 0) return this.slice(); if (n > 0) return this.slice(n); if (n < 0) return this.slice(0, this.length+n); } }); Object.defineProperty(String.prototype, 'strip_ext', { value: function() { return this.tolast('.'); } }); Object.defineProperty(String.prototype, 'ext', { value: function() { return this.fromlast('.'); } }); Object.defineProperty(String.prototype, 'set_ext', { value: function(val) { return this.strip_ext() + val; } }); Object.defineProperty(String.prototype, 'folder_same_name', { value: function() { var dirs = this.dir().split('/'); return dirs.last() === this.name(); } }); Object.defineProperty(String.prototype, 'up_path', { value: function() { var base = this.base(); var dirs = this.dir().split('/'); dirs.pop(); return dirs.join('/') + base; } }); Object.defineProperty(String.prototype, 'resolve', { value: function(path) { }, }); Object.defineProperty(String.prototype, 'fromlast', { value: function(val) { var idx = this.lastIndexOf(val); if (idx === -1) return ""; return this.slice(idx+1); } }); Object.defineProperty(String.prototype, 'tofirst', { value: function(val) { var idx = this.indexOf(val); if (idx === -1) return this.slice(); return this.slice(0,idx); } }); Object.defineProperty(String.prototype, 'fromfirst', { value: function(val) { var idx = this.indexOf(val); if (idx === -1) return this; return this.slice(idx+val.length); } }); Object.defineProperty(String.prototype, 'name', { value: function() { var idx = this.indexOf('/'); if (idx === -1) return this.tolast('.'); return this.fromlast('/').tolast('.'); } }); Object.defineProperty(String.prototype, 'set_name', { value: function(name) { var dir = this.dir(); return this.dir() + name + "." + this.ext(); } }); Object.defineProperty(String.prototype, 'base', { value: function() { return this.fromlast('/'); } }); Object.defineProperty(String.prototype, 'splice', { value: function(index, str) { return this.slice(0,index) + str + this.slice(index); } }); Object.defineProperty(String.prototype, 'sub', { value: function(index, str) { return this.slice(0,index) + str + this.slice(index+str.length); } }); Object.defineProperty(String.prototype, 'updir', { value: function() { if (this.lastIndexOf('/') === this.length-1) return this.slice(0,this.length-1); var dir = (this + "/").dir(); return dir.dir(); } }); Object.defineProperty(String.prototype, 'trimchr', { value: function(chars) { var start = this.length; var end = 0; for (var i = 0; i < this.length; i++) { if (!chars.includes(this[i])) { start = i; break; } } for (var i = this.length-1; i >= 0; i--) { if (!chars.includes(this[i])) { end = i+1; break; } } return this.substring(start,end); } }); Object.defineProperty(String.prototype, 'uc', { value: function() { return this.toUpperCase(); } }); Object.defineProperty(String.prototype, 'lc', {value:function() { return this.toLowerCase(); }}); /* ARRAY DEFS */ Object.defineProperty(Array.prototype, 'aspect', { value: function() { return this.x/this.y; } }); Object.defineProperty(Array.prototype, 'copy', { value: function() { var c = []; this.forEach(function(x, i) { c[i] = deep_copy(x); }); return c; } }); Object.defineProperty(Array.prototype, 'forFrom', { value: function(n, fn) { for (var i = n; i < this.length; i++) fn(this[i]); } }); Object.defineProperty(Array.prototype, 'forTo', { value: function(n, fn) { for (var i = 0; i < n; i++) fn(this[i]); } }); Object.defineProperty(Array.prototype, 'dofilter', { value: function(fn) { for (let i = 0; i < this.length; i++) { if (!fn.call(this, this[i], i, this)) { this.splice(i, 1); i--; } } return this; } }); Object.defineProperty(Array.prototype, 'reversed', { value: function() { var c = this.slice(); return c.reverse(); } }); Object.defineProperty(Array.prototype, 'rotate', { value: function(a) { return Vector.rotate(this, a); } }); function make_swizz() { function setelem(n) { return { get: function() { return this[n]; }, set: function(x) { this[n] = x; } } }; function arrsetelem(str, n) { Object.defineProperty(Array.prototype, str, setelem(n)); } var arr_elems = ['x', 'y', 'z', 'w']; var quat_elems = ['i', 'j', 'k']; var color_elems = ['r', 'g', 'b', 'a']; arr_elems.forEach(function(x, i) { arrsetelem(x,i); }); quat_elems.forEach(function(x, i) { arrsetelem(x,i); }); color_elems.forEach(function(x, i) { arrsetelem(x,i); }); var nums = [0,1,2,3]; var swizz = []; for (var i of nums) for (var j of nums) swizz.push([i,j]); swizz.forEach(function(x) { var str = ""; for (var i of x) str += arr_elems[i]; Object.defineProperty(Array.prototype, str, { get() { return [this[x[0]], this[x[1]]]; }, set(j) { this[x[0]] = j[0]; this[x[1]] = j[1]; }, }); str = ""; for (var i of x) str += color_elems[i]; Object.defineProperty(Array.prototype, str, { get() { return [this[x[0]], this[x[1]]]; }, set(j) { this[x[0]] = j[0]; this[x[1]] = j[1]; }, }); }); swizz = []; for (var i of nums) for (var j of nums) for (var k of nums) swizz.push([i,j,k]); swizz.forEach(function(x) { var str = ""; for (var i of x) str += arr_elems[i]; Object.defineProperty(Array.prototype, str, { get() { return [this[x[0]], this[x[1]], this[x[2]]]; }, set(j) { this[x[0]] = j[0]; this[x[1]] = j[1]; this[x[2]] = j[2];}, }); str = ""; for (var i of x) str += color_elems[i]; Object.defineProperty(Array.prototype, str, { get() { return [this[x[0]], this[x[1]], this[x[2]]]; }, set(j) { this[x[0]] = j[0]; this[x[1]] = j[1]; this[x[2]] = j[2];}, }); }); swizz = []; for (var i of nums) for (var j of nums) for (var k of nums) for (var w of nums) swizz.push([i,j,k,w]); swizz.forEach(function(x) { var str = ""; for (var i of x) str += arr_elems[i]; Object.defineProperty(Array.prototype, str, { get() { return [this[x[0]], this[x[1]], this[x[2]], this[x[3]]];}, set(j) { this[x[0]] = j[0]; this[x[1]] = j[1]; this[x[2]] = j[2]; this[x[3]] = j[3];}, }); str = ""; for (var i of x) str += color_elems[i]; Object.defineProperty(Array.prototype, str, { get() { return [this[x[0]], this[x[1]], this[x[2]], this[x[3]]];}, set(j) { this[x[0]] = j[0]; this[x[1]] = j[1]; this[x[2]] = j[2]; this[x[3]] = j[3];}, }); }); }; make_swizz(); Object.defineProperty(Array.prototype, 'add', { value: function(b) { var c = []; for (var i = 0; i < this.length; i++) { c[i] = this[i] + b[i]; } return c; }}); Object.defineProperty(Array.prototype, 'normalized', { value: function() { var c = this.slice(); var len = Vector.length(c); return c.map(v => v/len); } }); Object.defineProperty(Array.prototype, 'newfirst', { value: function(i) { var c = this.slice(); if (i >= c.length) return c; do { c.push(c.shift()); i--; } while (i > 0); return c; } }); Object.defineProperty(Array.prototype, 'doubleup', { value: function(n) { var c = []; this.forEach(function(x) { for (var i = 0; i < n; i++) c.push(x); }); return c; } }); Object.defineProperty(Array.prototype, 'sub', { value: function(b) { var c = []; for (var i = 0; i < this.length; i++) { c[i] = this[i] - b[i]; } return c; }}); Object.defineProperty(Array.prototype, 'mult', { value: function(arr) { var c = []; for (var i = 0; i < this.length; i++) { c[i] = this[i] * arr[i]; } return c; }}); Object.defineProperty(Array.prototype, 'apply', { value: function(fn) { this.forEach(function(x) { x[fn].apply(x); }); } }); Object.defineProperty(Array.prototype, 'scale', { value: function(s) { if (Array.isArray(s)) { var c = this.slice(); c.forEach(function(x,i) { c[i] = x * s[i]; }); return c; } return this.map(function(x) { return x*s; }); }}); Object.defineProperty(Array.prototype, 'sorted', { value: function() { return this.toSorted(); } }); Object.defineProperty(Array.prototype, 'equal', { value: function(b) { if (this.length !== b.length) return false; if (b == null) return false; if (this === b) return true; return JSON.stringify(this.sorted()) === JSON.stringify(b.sorted()); for (var i = 0; i < this.length; i++) { if (!this[i] === b[i]) return false; } return true; }}); Object.defineProperty(Array.prototype, 'mapc', { value: function(fn) { return this.map(x => fn(x)); } }); Object.defineProperty(Array.prototype, 'mapvec', { value: function(fn, b) { return this.map((x,i) => fn(x,b[i])); } }); Object.defineProperty(Array.prototype, 'remove', { value: function(b) { var idx = this.indexOf(b); if (idx === -1) return false; this.splice(idx, 1); return true; }}); Object.defineProperty(Array.prototype, 'set', { value: function(b) { if (this.length !== b.length) return; b.forEach(function(val, i) { this[i] = val; }, this); } }); Object.defineProperty(Array.prototype, 'flat', { value: function() { return [].concat.apply([],this); } }); Object.defineProperty(Array.prototype, 'anyjs', { value: function(fn) { var ev = this.every(function(x) { return !fn(x); }); return !ev; } }); /* Return true if array contains x */ Object.defineProperty(Array.prototype, 'empty', { get: function() { return this.length === 0; }, }); Object.defineProperty(Array.prototype, 'push_unique', { value: function(x) { if (!this.includes(x)) this.push(x); }}); Object.defineProperty(Array.prototype, 'unique', { value: function() { var c = []; this.forEach(function(x) { c.push_unique(x); }); return c; } }); Object.defineProperty(Array.prototype, 'unduped', { value: function() { return [... new Set(this)]; } }); Object.defineProperty(Array.prototype, 'findIndex', { value: function(fn) { var idx = -1; this.every(function(x, i) { if (fn(x)) { idx = i; return false; } return true; }); return idx; }}); Object.defineProperty(Array.prototype, 'find', { value: function(fn) { var ret; this.every(function(x) { if (fn(x)) { ret = x; return false; } return true; }); return ret; }}); Object.defineProperty(Array.prototype, 'search', { value: function(val) { for (var i = 0; i < this.length; i++) if (this[i] === val) return i; return undefined; } }); Object.defineProperty(Array.prototype, 'last', { value: function() { return this[this.length-1]; }, }); Object.defineProperty(Array.prototype, 'at', { value: function(x) { return x < 0 ? this[this.length+x] : this[x]; }}); Object.defineProperty(Array.prototype, 'wrapped', { value: function(x) { var c = this.slice(0, this.length); for (var i = 0; i < x; i++) c.push(this[i]); return c; }}); Object.defineProperty(Array.prototype, 'wrap_idx', { value: function(x) { while (x >= this.length) { x -= this.length; } return x; } }); Object.defineProperty(Array.prototype, 'mirrored', { value: function(x) { var c = this.slice(0); if (c.length <= 1) return c; for (var i = c.length-2; i >= 0; i--) c.push(c[i]); return c; } }); Object.defineProperty(Array.prototype, 'lerp', { value: function(to, t) { var c = []; this.forEach(function(x,i) { c[i] = (to[i] - x) * t + x; }); return c; } }); Math.lerp = function(s,f,t) { return (f-s)*t + s; }; Math.gcd = function(a,b) { return b === 0 ? a : gcd(b,a%b); } Math.lcm = function(a,b) { return (a*b)/gcd(a,b); } Math.grab_from_points = function(pos, points, slop) { var shortest = slop; var idx = -1; points.forEach(function(x,i) { if (Vector.length(pos.sub(x)) < shortest) { shortest = Vector.length(pos.sub(x)); idx = i; } }); return idx; }; Math.nearest = function(n, incr) { return Math.round(n/incr)*incr; } Math.places = function(n,digits) { var div = Math.pow(10,digits); return Math.round(n*div)/div; } Number.hex = function(n) { var s = Math.floor(n).toString(16); if (s.length === 1) s = '0' + s; return s.uc(); } Object.defineProperty(Object.prototype, 'lerp',{ value: function(to, t) { var self = this; var obj = {}; Object.keys(self).forEach(function(key) { obj[key] = self[key].lerp(to[key],t); }); return obj; }}); /* MATH EXTENSioNS */ Object.defineProperty(Number.prototype, 'lerp', { value: function(to, t) { var s = this; return (to - this) * t + this; } }); Object.defineProperty(Number.prototype, 'clamp', { value: function(from,to) { return Math.clamp(this,from,to); } }); Math.clamp = function (x, l, h) { return x > h ? h : x < l ? l : x; } Math.random_range = function(min,max) { return Math.random() * (max-min) + min; }; Math.rand_int = function(max) { return Math.floor(Math.random()*max); }; Math.snap = function(val, grid) { if (!grid || grid === 1) return Math.round(val); var rem = val%grid; var d = val - rem; var i = Math.round(rem/grid)*grid; return d+i; } Math.angledist = function (a1, a2) { a1 = a1%1; a2 = a2%1; var dist = a2 - a1; if (dist == 0) return dist; if (dist > 0) { if (dist > 0.5) return dist-1; return dist; } if (dist < -0.5) return dist+1; return dist; }; Math.angledist.doc = "Find the shortest angle between two angles."; Math.TAU = Math.PI*2; Math.deg2rad = function(deg) { return deg * 0.0174533; }; Math.rad2deg = function(rad) { return rad / 0.0174533; }; Math.deg2rad = function(x) { return x; }; Math.rad2deg = function(x) { return x; }; Math.turn2rad = function(x) { return x*Math.TAU; }; Math.rad2turn = function(x) { return x/Math.TAU; }; Math.turn2deg = function(x) { return x*360; }; Math.deg2turn = function(x) { return x/360; }; Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * max), 0, max-1); }; Math.variate = function(n, pct) { return n + (Math.random_range(-pct,pct)*n); } /* BOUNDINGBOXES */ var bbox = {}; bbox.overlap = function(box1, box2) { return ( box1.l > box2.l && box1.r < box2.r && box1.t < box2.t && box1.b > box2.b ); return ( box1.l > box2.r || box1.r < box2.l || box1.t > box2.b || box1.b < box2.t ); } bbox.fromcwh = function(c, wh) { return { t: c.y+(wh.y/2), b: c.y-(wh.y/2), l: c.x-(wh.x/2), r: c.x+(wh.x/2) }; }; bbox.frompoints = function(points) { var b= {t:0,b:0,l:0,r:0}; points.forEach(function(x) { if (x.y > b.t) b.t = x.y; if (x.y < b.b) b.b = x.y; if (x.x > b.r) b.r = x.x; if (x.x < b.l) b.l = x.x; }); return b; }; bbox.topoints = function(bb) { return [ [bb.l,bb.t], [bb.r,bb.t], [bb.r,bb.b], [bb.l,bb.b] ]; } bbox.tocwh = function(bb) { if (!bb) return undefined; var cwh = {}; var w = bb.r - bb.l; var h = bb.t - bb.b; cwh.wh = [w, h]; cwh.c = [bb.l + w/2, bb.b + h/2]; return cwh; }; bbox.towh = function(bb) { return [bb.r-bb.l, bb.t-bb.b]; }; bbox.pointin = function(bb, p) { if (bb.t < p.y || bb.b > p.y || bb.l > p.x || bb.r < p.x) return false; return true; } bbox.zero = function(bb) { var newbb = Object.assign({},bb); newbb.r -= newbb.l; newbb.t -= newbb.b; newbb.b = 0; newbb.l = 0; return newbb; } bbox.move = function(bb, pos) { var newbb = Object.assign({}, bb); newbb.t += pos.y; newbb.b += pos.y; newbb.l += pos.x; newbb.r += pos.x; return newbb; }; bbox.moveto = function(bb, pos) { bb = bbox.zero(bb); return bbox.move(bb, pos); } bbox.expand = function(oldbb, x) { if (!oldbb || !x) return; var bb = {}; Object.assign(bb, oldbb); if (bb.t < x.t) bb.t = x.t; if (bb.r < x.r) bb.r = x.r; if (bb.b > x.b) bb.b = x.b; if (bb.l > x.l) bb.l = x.l; return bb; }; bbox.blwh = function(bl,wh) { return { b: bl.y, l: bl.x, r: bl.x + wh.x, t: bl.y + wh.y }; } bbox.blwh.doc = "Bounding box from (bottom left, width height)"; bbox.fromobjs = function(objs) { var bb = objs[0].boundingbox; objs.forEach(function(obj) { bb = bbox.expand(bb, obj.boundingbox); }); return bb; }; /* VECTORS */ var Vector = {}; Vector.length = function(v) { return Math.hypot(...v); } Vector.norm = function(v) { var len = Vector.length(v); if (!len) return [0,0]; return [v.x/len, v.y/len]; } Vector.project = function(a, b) { return vector.project(a,b); } Vector.dot = function(a, b) { return vector.dot(a,b); }, Vector.random = function() { var vec = [Math.random()-0.5, Math.random()-0.5]; return Vector.norm(vec); } Vector.midpoint = function(a,b) { return [(a.x+b.x)/2, (a.y+b.y)/2]; } Vector.distance = function(a,b) { return Math.hypot(b.x-a.x, b.y-a.y); } Vector.angle_between = function(a,b) { var dot = Vector.dot(a,b); var am = Vector.length(a); var bm = Vector.length(b); var cos_a = dot / (am*bm); var angle = Math.acos(cos_a); return Math.rad2turn(angle); } Vector.angle = function(v) { return Math.rad2turn(Math.atan2(v.y, v.x)); } Vector.rotate = function(v,angle) { var r = Vector.length(v); angle += Vector.angle(v); angle = Math.turn2rad(angle); return [r*Math.cos(angle), r*Math.sin(angle)]; } Vector.equal = function(v1, v2, tol) { if (!tol) return v1.equal(v2); var eql = true; var c = v1.sub(v2); c.forEach(function(x) { if (!eql) return; if (Math.abs(x) > tol) eql = false; }); return eql; } Vector.reflect = function(vec, plane) { var p = Vector.norm(plane); return vec.sub(p.scale(2*Vector.dot(vec, p))); } Vector.reflect_point = function(vec, point) { return point.add(vec.sub(point).scale(-1)); } /* POINT ASSISTANCE */ function points2cm(points) { var x = 0; var y = 0; var n = points.length; points.forEach(function(p) { x = x + p[0]; y = y + p[1]; }); return [x/n,y/n]; }; Math.sortpointsccw = function(points) { var cm = points2cm(points); var cmpoints = points.map(function(x) { return x.sub(cm); }); var ccw = cmpoints.sort(function(a,b) { var aatan = Math.atan2(a.y, a.x); var batan = Math.atan2(b.y, b.x); return aatan - batan; }); return ccw.map(function(x) { return x.add(cm); }); } var yaml = {}; yaml.tojson = function(yaml) { // Replace key value pairs that are strings with quotation marks around them yaml = yaml.replace(/(\w+):/g, '"$1":'); yaml = yaml.replace(/: ([\w\.\/]+)/g, ': "$1"'); // TODO: make this more general yaml = yaml.split("\n"); var cont = {}; var cur = 0; for (var i = 0; i < yaml.length; i++) { var line = yaml[i]; var indent = line.search(/\S/); if (indent > cur) { if (line[indent] == "-") { cont[indent] = "array"; yaml[i] = line.sub(indent, '['); } else { cont[indent] = "obj"; yaml[i] = line.sub(indent-1, '{'); } } if (indent < cur) { while (cur > indent) { if (cont[cur] === "obj") yaml[i-1] = yaml[i-1] + "}"; else if (cont[cur] === "array") yaml[i-1] = yaml[i-1] + "]"; delete cont[cur]; cur--; } } if (indent === cur) { if (yaml[i][indent] === '-') yaml[i] = yaml[i].sub(indent,','); else yaml[i-1] = yaml[i-1] + ','; } cur = indent; } yaml = "{" + yaml.join("\n") + "}"; yaml = yaml.replace(/\s/g, ''); yaml = yaml.replace(/,}/g, '}'); yaml = yaml.replace(/,]/g, ']'); yaml = yaml.replace(/,"[^"]+"\:,/g, ','); return yaml; } Math.sign = function(n) { return n >= 0 ? 1 : -1; } return { convert, time, Vector, bbox, yaml };