2023-09-19 17:37:54 -05:00
|
|
|
/* 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.
|
|
|
|
*/
|
2023-12-18 06:45:27 -06:00
|
|
|
/*
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.getPrototypeOf = undefined;
|
|
|
|
Object.setPrototypeOf = undefined;
|
|
|
|
Reflect = undefined;
|
|
|
|
Symbol = undefined;
|
|
|
|
URIError = undefined;
|
|
|
|
Proxy = undefined;
|
|
|
|
Map = undefined;
|
|
|
|
WeakMap = undefined;
|
|
|
|
Promise = undefined;
|
|
|
|
Set = undefined;
|
|
|
|
WeakSet = undefined;
|
2023-12-18 06:45:27 -06:00
|
|
|
*/
|
2023-10-09 13:03:12 -05:00
|
|
|
|
2023-12-09 22:09:15 -06:00
|
|
|
var fmt = {};
|
|
|
|
|
2024-02-09 01:49:52 -06:00
|
|
|
function arabic2roman(num)
|
|
|
|
{
|
|
|
|
if (num <= 0 || num >= 4000)
|
|
|
|
return "Invalid input. Roman numerals are not defined for numbers less than 1 or greater than 3999.";
|
|
|
|
|
|
|
|
var roman_numerals = {
|
|
|
|
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 result = '';
|
|
|
|
|
|
|
|
for (var key in roman_numerals) {
|
|
|
|
while (num >= roman_numerals[key]) {
|
|
|
|
result += key;
|
|
|
|
num -= roman_numerals[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-12-09 22:09:15 -06:00
|
|
|
var timeparse = {
|
|
|
|
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",
|
|
|
|
};
|
|
|
|
|
|
|
|
String.parse = function(str, p)
|
|
|
|
{
|
|
|
|
var rec = {};
|
|
|
|
var fmts = Object.keys(p).sort(function(a,b) { return a.length > b.length; });
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Time values are always expressed in terms of real earth-seconds */
|
|
|
|
var time = {
|
|
|
|
get hour2minute() { return this.hour/this.minute; },
|
|
|
|
get day2hour() { return this.day/this.hour; },
|
|
|
|
get minute2second() { return this.minute/this.second; },
|
|
|
|
get week2day() { return this.week/this.day; },
|
|
|
|
};
|
|
|
|
|
|
|
|
time.second = 1; /* earth-seconds in a second */
|
|
|
|
time.minute = 60; /* seconds in a minute */
|
|
|
|
time.hour = 3_600; /* seconds in an hour */
|
|
|
|
time.day = 86_400; /* seconds in a day */
|
|
|
|
time.week = 604_800; /* seconds in a week */
|
|
|
|
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; /* Times are expressed in terms of day 0 at hms 0 of this year */
|
|
|
|
time.now = function() { return cmd(210);}
|
|
|
|
time.computer_zone = function() { return cmd(211)/this.hour; }
|
|
|
|
time.computer_dst = function() { return cmd(212); }
|
|
|
|
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.yearsize.doc = "Given a year, return the number of days in that year.";
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (typeof num === 'object') return num;
|
|
|
|
else if (typeof num === 'number') {
|
|
|
|
zone ??= this.computer_zone();
|
|
|
|
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, zone)
|
|
|
|
{
|
|
|
|
fmt ??= this.fmt;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-11-29 12:40:13 -06:00
|
|
|
var json = {};
|
|
|
|
json.encode = function(value, space, replacer, whitelist)
|
|
|
|
{
|
|
|
|
return JSON.stringify(value, space, replacer);
|
|
|
|
}
|
|
|
|
|
|
|
|
json.decode = function(text, reviver)
|
|
|
|
{
|
|
|
|
return JSON.parse(text,reviver);
|
|
|
|
}
|
|
|
|
|
2024-01-14 10:24:31 -06:00
|
|
|
json.readout = function(obj)
|
|
|
|
{
|
|
|
|
var j = {};
|
|
|
|
for (var k in obj)
|
|
|
|
if (typeof obj[k] === 'function')
|
|
|
|
j[k] = 'function ' + obj[k].toString();
|
|
|
|
else
|
|
|
|
j[k] = obj[k];
|
|
|
|
|
|
|
|
return json.encode(j);
|
|
|
|
}
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.methods = function(o)
|
|
|
|
{
|
|
|
|
var m = [];
|
|
|
|
Object.keys(o).forEach(function(k) {
|
|
|
|
if (typeof o[k] === 'function') m.push(k);
|
|
|
|
});
|
|
|
|
return m;
|
|
|
|
}
|
2023-11-29 12:40:13 -06:00
|
|
|
Object.methods.doc = "Retun an array of all functions an object has access to.";
|
2023-10-09 13:03:12 -05:00
|
|
|
|
2023-10-18 17:20:23 -05:00
|
|
|
Object.dig = function(obj, path, def)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-11-29 12:40:13 -06:00
|
|
|
Object.samenewkeys = function(a,b)
|
|
|
|
{
|
|
|
|
b ??= a.__proto__;
|
|
|
|
var ret = {};
|
|
|
|
ret.same = [];
|
|
|
|
ret.unique = [];
|
|
|
|
Object.keys(a).forEach(key => (key in b) ? ret.same.push(key) : ret.unique.push(key));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
Object.samenewkeys.doc = "Return an object listing which keys are the same and unique on a compared to b.";
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
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;
|
|
|
|
}
|
2023-09-19 17:37:54 -05:00
|
|
|
|
2023-10-31 08:31:56 -05:00
|
|
|
Object.extend = function(from)
|
2023-10-11 17:22:41 -05:00
|
|
|
{
|
|
|
|
var n = {};
|
|
|
|
Object.mixin(n, from);
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2023-10-05 08:02:12 -05:00
|
|
|
Object.mixin = function(target, source)
|
2023-09-20 17:58:18 -05:00
|
|
|
{
|
2023-10-05 13:33:43 -05:00
|
|
|
if (typeof source !== 'object') return target;
|
2023-10-16 09:40:43 -05:00
|
|
|
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
2023-09-20 17:58:18 -05:00
|
|
|
return target;
|
2023-04-22 16:44:26 -05:00
|
|
|
};
|
|
|
|
|
2023-10-31 08:31:56 -05:00
|
|
|
Object.mix = function(...objs)
|
|
|
|
{
|
|
|
|
var n = {};
|
|
|
|
for (var o of objs)
|
|
|
|
Object.mixin(n,o);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2023-10-11 17:22:41 -05:00
|
|
|
Object.deepmixin = function(target, source)
|
|
|
|
{
|
|
|
|
var o = source;
|
|
|
|
while (o !== Object.prototype) {
|
|
|
|
Object.mixin(target, o);
|
|
|
|
o = o.__proto__;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-21 12:50:39 -05:00
|
|
|
Object.deepfreeze = function(obj)
|
|
|
|
{
|
|
|
|
for (var key in obj) {
|
2023-09-22 09:44:58 -05:00
|
|
|
if (typeof obj[key] === 'object')
|
2023-09-21 12:50:39 -05:00
|
|
|
Object.deepfreeze(obj[key]);
|
|
|
|
}
|
|
|
|
Object.freeze(obj);
|
|
|
|
}
|
|
|
|
|
2023-10-05 13:33:43 -05:00
|
|
|
/* Goes through each key and overwrites if it's present */
|
2023-10-10 17:37:58 -05:00
|
|
|
Object.dainty_assign = function(target, source)
|
|
|
|
{
|
|
|
|
Object.keys(source).forEach(function(k) {
|
2023-10-17 12:22:06 -05:00
|
|
|
if (typeof source[k] === 'function') return;
|
2023-10-10 17:37:58 -05:00
|
|
|
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];
|
2023-10-05 13:33:43 -05:00
|
|
|
});
|
2023-09-20 17:58:18 -05:00
|
|
|
}
|
|
|
|
|
2023-10-04 17:57:37 -05:00
|
|
|
Object.isObject = function(o)
|
|
|
|
{
|
|
|
|
return (typeof o === 'object' && !Array.isArray(o));
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.setter_assign = function(target, source)
|
|
|
|
{
|
|
|
|
for (var key in target)
|
|
|
|
if (Object.isAccessor(target,key) && typeof source[key] !== 'undefined')
|
|
|
|
target[key] = source[key];
|
|
|
|
}
|
|
|
|
|
2023-09-25 16:34:48 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-09-21 19:51:38 -05:00
|
|
|
Object.isAccessor = function(obj, prop)
|
|
|
|
{
|
2023-09-25 16:34:48 -05:00
|
|
|
var o = Object.containingKey(obj,prop);
|
|
|
|
if (!o) return false;
|
|
|
|
|
|
|
|
var desc = Object.getOwnPropertyDescriptor(o,prop);
|
2023-09-23 12:35:02 -05:00
|
|
|
if (!desc) return false;
|
|
|
|
if (desc.get || desc.set) return true;
|
|
|
|
return false;
|
2023-09-21 19:51:38 -05:00
|
|
|
}
|
|
|
|
|
2023-09-23 12:35:02 -05:00
|
|
|
Object.mergekey = function(o1,o2,k)
|
|
|
|
{
|
2023-09-27 09:37:20 -05:00
|
|
|
if (!o2) return;
|
2023-12-27 07:04:18 -06:00
|
|
|
if (typeof o2[k] === 'object') {
|
2023-09-23 12:35:02 -05:00
|
|
|
if (Array.isArray(o2[k]))
|
2023-09-27 17:40:04 -05:00
|
|
|
o1[k] = deep_copy(o2[k]);
|
2023-09-24 11:26:44 -05:00
|
|
|
else {
|
2023-09-25 16:34:48 -05:00
|
|
|
if (!o1[k]) o1[k] = {};
|
2023-09-27 09:37:20 -05:00
|
|
|
if (typeof o1[k] === 'object')
|
|
|
|
Object.merge(o1[k], o2[k]);
|
|
|
|
else
|
|
|
|
o1[k] = o2[k];
|
2023-09-24 11:26:44 -05:00
|
|
|
}
|
2023-09-23 12:35:02 -05:00
|
|
|
} else
|
2023-12-27 07:04:18 -06:00
|
|
|
Object.defineProperty(o1, k, Object.getOwnPropertyDescriptor(o2,k));
|
|
|
|
// o1[k] = o2[k];
|
2023-09-23 12:35:02 -05:00
|
|
|
}
|
2023-09-21 19:51:38 -05:00
|
|
|
|
|
|
|
/* Same as merge from Ruby */
|
|
|
|
Object.merge = function(target, ...objs)
|
|
|
|
{
|
|
|
|
for (var obj of objs)
|
2023-09-23 12:35:02 -05:00
|
|
|
for (var key of Object.keys(obj))
|
|
|
|
Object.mergekey(target,obj,key);
|
2023-10-02 17:03:01 -05:00
|
|
|
|
|
|
|
return target;
|
2023-09-21 19:51:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Object.totalmerge = function(target, ...objs)
|
|
|
|
{
|
2023-09-23 12:35:02 -05:00
|
|
|
for (var obj of objs)
|
|
|
|
for (var key in obj)
|
|
|
|
Object.mergekey(target,obj,key);
|
2023-12-27 07:04:18 -06:00
|
|
|
|
|
|
|
return target;
|
2023-09-21 19:51:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns a new object with undefined, null, and empty values removed. */
|
|
|
|
Object.compact = function(obj)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
2023-09-21 12:50:39 -05:00
|
|
|
|
2023-09-20 13:33:11 -05:00
|
|
|
Object.totalassign = function(to, from)
|
|
|
|
{
|
|
|
|
for (var key in from)
|
|
|
|
to[key] = from[key];
|
|
|
|
}
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
/* Prototypes out an object and assigns values */
|
2023-09-20 17:58:18 -05:00
|
|
|
Object.copy = function(proto, ...objs)
|
|
|
|
{
|
2023-04-22 16:44:26 -05:00
|
|
|
var c = Object.create(proto);
|
2023-09-20 17:58:18 -05:00
|
|
|
for (var obj of objs)
|
2023-10-05 08:02:12 -05:00
|
|
|
Object.mixin(c, obj);
|
2023-04-22 16:44:26 -05:00
|
|
|
return c;
|
2023-09-20 17:58:18 -05:00
|
|
|
}
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
/* OBJECT DEFININTIONS */
|
2023-09-19 12:35:12 -05:00
|
|
|
Object.defHidden = function(obj, prop)
|
|
|
|
{
|
|
|
|
Object.defineProperty(obj, prop, {enumerable:false, writable:true});
|
|
|
|
}
|
|
|
|
|
2023-10-05 08:02:12 -05:00
|
|
|
Object.hide = function(obj,...props)
|
2023-10-02 17:03:01 -05:00
|
|
|
{
|
2023-10-05 08:02:12 -05:00
|
|
|
for (var prop of props) {
|
|
|
|
var p = Object.getOwnPropertyDescriptor(obj,prop);
|
|
|
|
if (!p) {
|
2023-12-18 17:12:05 -06:00
|
|
|
Log.info(`No property of name ${prop}.`);
|
|
|
|
continue;
|
2023-10-05 08:02:12 -05:00
|
|
|
}
|
|
|
|
p.enumerable = false;
|
|
|
|
Object.defineProperty(obj, prop, p);
|
2023-10-02 17:03:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-26 11:48:02 -05:00
|
|
|
Object.unhide = function(obj, ...props)
|
|
|
|
{
|
|
|
|
for (var prop of props) {
|
|
|
|
var p = Object.getOwnPropertyDescriptor(obj,prop);
|
|
|
|
if (!p) {
|
|
|
|
Log.warn(`No property of name ${prop}.`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
p.enumerable = true;
|
|
|
|
Object.defineProperty(obj, prop, p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-29 10:07:58 -05:00
|
|
|
Object.defineProperty(Object.prototype, 'obscure', {
|
|
|
|
value: function(name) {
|
|
|
|
Object.defineProperty(this, name, { enumerable: false });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-05-27 07:01:17 -05:00
|
|
|
Object.defineProperty(Object.prototype, 'prop_obj', {
|
|
|
|
value: function() { return JSON.parse(JSON.stringify(this)); }
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
/* 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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-05-27 07:01:17 -05:00
|
|
|
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});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
Object.defineProperty(Object.prototype, 'deflock', {
|
|
|
|
value: function(prop) {
|
|
|
|
Object.defineProperty(this,prop, {configurable:false});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(Object.prototype, 'forEach', {
|
|
|
|
value: function(fn) {
|
2023-09-29 13:16:59 -05:00
|
|
|
Object.values(this).forEach(fn);
|
2023-04-22 16:44:26 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-15 16:42:39 -06:00
|
|
|
Object.defineProperty(Object.prototype, 'map', {
|
|
|
|
value: function(fn) {
|
|
|
|
var a = [];
|
|
|
|
Object.values(this).forEach(function(x) {
|
|
|
|
a.push(fn(x));
|
|
|
|
});
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
Object.defineProperty(Object.prototype, 'empty', {
|
|
|
|
get: function() {
|
|
|
|
return Object.keys(this).empty;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(Object.prototype, 'nth', {
|
|
|
|
value: function(x) {
|
|
|
|
if (this.empty || x >= Object.keys(this).length) return null;
|
|
|
|
|
|
|
|
return this[Object.keys(this)[x]];
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-09-29 13:16:59 -05:00
|
|
|
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;
|
2023-10-02 07:58:17 -05:00
|
|
|
var t = str;
|
2023-09-29 13:16:59 -05:00
|
|
|
while (Object.hasOwn(this,t)) {
|
|
|
|
t = str + n;
|
2023-10-02 07:58:17 -05:00
|
|
|
n++;
|
2023-09-29 13:16:59 -05:00
|
|
|
}
|
|
|
|
this[t] = val;
|
2023-10-02 07:58:17 -05:00
|
|
|
return t;
|
2023-09-29 13:16:59 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(Object.prototype, 'remove', {
|
|
|
|
value: function(val) {
|
2023-10-02 07:58:17 -05:00
|
|
|
delete this[val.toString()];
|
2023-09-29 13:16:59 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-09-26 13:34:02 -05:00
|
|
|
Object.defineProperty(String.prototype, 'strip_ext', {
|
2023-10-09 13:03:12 -05:00
|
|
|
value: function() { return this.tolast('.'); }
|
2023-09-26 13:34:02 -05:00
|
|
|
});
|
2023-04-22 16:44:26 -05:00
|
|
|
|
2023-09-08 01:26:48 -05:00
|
|
|
Object.defineProperty(String.prototype, 'ext', {
|
2023-10-09 13:03:12 -05:00
|
|
|
value: function() { return this.fromlast('.'); }
|
2023-09-11 02:46:12 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(String.prototype, 'set_ext', {
|
2023-10-09 13:03:12 -05:00
|
|
|
value: function(val) { return this.strip_ext() + val; }
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(String.prototype, 'fromlast', {
|
2023-09-11 02:46:12 -05:00
|
|
|
value: function(val) {
|
2023-10-09 13:03:12 -05:00
|
|
|
var idx = this.lastIndexOf(val);
|
|
|
|
if (idx === -1) return "";
|
|
|
|
return this.slice(idx+1);
|
2023-09-08 01:26:48 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.defineProperty(String.prototype, 'tolast', {
|
|
|
|
value: function(val) {
|
|
|
|
var idx = this.lastIndexOf(val);
|
|
|
|
if (idx === -1) return this.slice();
|
|
|
|
return this.slice(0,idx);
|
2023-09-08 01:26:48 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.defineProperty(String.prototype, 'tofirst', {
|
|
|
|
value: function(val) {
|
|
|
|
var idx = this.indexOf(val);
|
|
|
|
if (idx === -1) return this.slice();
|
|
|
|
return this.slice(0,idx);
|
2023-09-08 01:26:48 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-17 12:22:06 -05:00
|
|
|
Object.defineProperty(String.prototype, 'fromfirst', {
|
|
|
|
value: function(val) {
|
|
|
|
var idx = this.indexOf(val);
|
|
|
|
if (idx === -1) return this;
|
2023-11-22 03:51:43 -06:00
|
|
|
return this.slice(idx+val.length);
|
2023-10-17 12:22:06 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.defineProperty(String.prototype, 'name', {
|
2023-10-10 17:37:58 -05:00
|
|
|
value: function() {
|
|
|
|
var idx = this.indexOf('/');
|
|
|
|
if (idx === -1)
|
|
|
|
return this.tolast('.');
|
|
|
|
return this.fromlast('/').tolast('.'); }
|
2023-10-09 13:03:12 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(String.prototype, 'base', {
|
|
|
|
value: function() { return this.fromlast('/'); }
|
|
|
|
});
|
|
|
|
|
2023-09-08 01:26:48 -05:00
|
|
|
Object.defineProperty(String.prototype, 'dir', {
|
2023-10-09 13:03:12 -05:00
|
|
|
value: function() { return this.tolast('/'); }
|
2023-09-08 01:26:48 -05:00
|
|
|
});
|
|
|
|
|
2023-11-17 15:16:13 -06:00
|
|
|
Object.defineProperty(String.prototype, 'splice', {
|
|
|
|
value: function(index, str, ) {
|
|
|
|
return this.slice(0,index) + str + this.slice(index);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(String.prototype, 'rm', {
|
|
|
|
value: function(index, endidx) {
|
|
|
|
endidx ??= index+1;
|
|
|
|
return this.slice(0,index) + this.slice(endidx);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-09-26 17:07:51 -05:00
|
|
|
Object.defineProperty(String.prototype, 'updir', {
|
|
|
|
value: function() {
|
2023-10-02 07:58:17 -05:00
|
|
|
if (this.lastIndexOf('/') === this.length-1)
|
|
|
|
return this.slice(0,this.length-1);
|
|
|
|
|
|
|
|
var dir = (this + "/").dir();
|
2023-09-26 17:07:51 -05:00
|
|
|
return dir.dir();
|
|
|
|
}
|
|
|
|
});
|
2023-09-08 01:26:48 -05:00
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.defineProperty(String.prototype, 'startswith', {
|
|
|
|
value: function(val) {
|
|
|
|
if (!val) return false;
|
|
|
|
return this.startsWith(val);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(String.prototype, 'endswith', {
|
|
|
|
value: function(val) {
|
|
|
|
if (!val) return false;
|
|
|
|
return this.endsWith(val);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-27 14:29:55 -06:00
|
|
|
Object.defineProperty(String.prototype, 'pct', {
|
|
|
|
value: function(val) {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.defineProperty(String.prototype, 'uc', { value: function() { return this.toUpperCase(); } });
|
|
|
|
Object.defineProperty(String.prototype, 'lc', {value:function() { return this.toLowerCase(); }});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
/* ARRAY DEFS */
|
|
|
|
Object.defineProperty(Array.prototype, 'copy', {
|
|
|
|
value: function() {
|
|
|
|
var c = [];
|
|
|
|
|
|
|
|
this.forEach(function(x, i) {
|
|
|
|
c[i] = deep_copy(x);
|
|
|
|
});
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Object.defineProperty(Array.prototype, 'dofilter', {
|
|
|
|
value: function(fn) {
|
|
|
|
var j = 0;
|
|
|
|
this.forEach(function(val,i) {
|
|
|
|
if (fn(val)) {
|
|
|
|
if (i !== j) this[j] = val;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
this.length = j;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function filterInPlace(a, condition, thisArg) {
|
|
|
|
let j = 0;
|
|
|
|
|
|
|
|
a.forEach((e, i) => {
|
|
|
|
if (condition.call(thisArg, e, i, a)) {
|
|
|
|
if (i!==j) a[j] = e;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
a.length = j;
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-09-27 17:40:04 -05:00
|
|
|
Object.defineProperty(Array.prototype, 'reversed', {
|
|
|
|
value: function() {
|
|
|
|
var c = this.slice();
|
|
|
|
return c.reverse();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
Object.defineProperty(Array.prototype, 'rotate', {
|
|
|
|
value: function(a) {
|
|
|
|
return Vector.rotate(this, a);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2023-11-06 07:05:27 -06:00
|
|
|
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];},
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
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;
|
|
|
|
}});
|
|
|
|
|
2023-12-19 17:28:45 -06:00
|
|
|
Object.defineProperty(Array.prototype, 'normalized', {
|
|
|
|
value: function() {
|
|
|
|
var c = this.slice();
|
|
|
|
var len = Vector.length(c);
|
|
|
|
return c.map(v => v/len);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
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;
|
|
|
|
}});
|
|
|
|
|
2023-05-24 20:45:50 -05:00
|
|
|
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;
|
|
|
|
}});
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
Object.defineProperty(Array.prototype, 'apply', {
|
|
|
|
value: function(fn) {
|
|
|
|
this.forEach(function(x) { x[fn].apply(x); });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(Array.prototype, 'scale', {
|
|
|
|
value: function(s) {
|
2023-06-01 15:58:56 -05:00
|
|
|
if (Array.isArray(s)) {
|
|
|
|
var c = this.slice();
|
|
|
|
c.forEach(function(x,i) { c[i] = x * s[i]; });
|
|
|
|
return c;
|
|
|
|
}
|
2023-04-22 16:44:26 -05:00
|
|
|
return this.map(function(x) { return x*s; });
|
|
|
|
}});
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-10-02 17:03:01 -05:00
|
|
|
return JSON.stringify(this.sort()) === JSON.stringify(b.sort());
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
for (var i = 0; i < this.length; i++) {
|
|
|
|
if (!this[i] === b[i])
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}});
|
|
|
|
|
|
|
|
function add(x,y) { return x+y; };
|
|
|
|
function mult(x,y) { return x*y; };
|
|
|
|
|
|
|
|
Object.defineProperty(Array.prototype, 'mapc', {
|
2023-10-02 07:58:17 -05:00
|
|
|
value: function(fn) {
|
|
|
|
return this.map(x => fn(x));
|
2023-04-22 16:44:26 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-12-24 09:14:46 -06:00
|
|
|
Object.defineProperty(Array.prototype, 'mapvec', {
|
|
|
|
value: function(fn, b) {
|
|
|
|
return this.map((x,i) => fn(x,b[i]));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
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, 'any', {
|
|
|
|
value: function(fn) {
|
|
|
|
var ev = this.every(function(x) {
|
|
|
|
return !fn(x);
|
|
|
|
});
|
|
|
|
return !ev;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/* Return true if array contains x */
|
|
|
|
/*Object.defineProperty(Array.prototype, 'includes', {
|
|
|
|
value: function(x) {
|
|
|
|
return this.some(e => e === 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, '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, 'last', {
|
2023-12-18 06:45:27 -06:00
|
|
|
value: function() { return this[this.length-1]; },
|
2023-04-22 16:44:26 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-06-05 17:19:43 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-09-11 02:46:12 -05:00
|
|
|
Math.lerp = function(s,f,t) { return (f-s)*t + s; };
|
2023-12-09 22:09:15 -06:00
|
|
|
Math.gcd = function(a,b) { return b === 0 ? a : gcd(b,a%b); }
|
|
|
|
Math.lcm = function(a,b) { return (a*b)/gcd(a,b); }
|
2023-09-11 02:46:12 -05:00
|
|
|
|
2023-11-29 17:31:41 -06:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2023-12-20 17:20:29 -06:00
|
|
|
Math.nearest = function(n, incr)
|
|
|
|
{
|
|
|
|
return Math.round(n/incr)*incr;
|
|
|
|
}
|
2023-11-29 17:31:41 -06:00
|
|
|
|
2023-12-27 10:34:14 -06:00
|
|
|
Math.places = function(n,digits)
|
|
|
|
{
|
|
|
|
var div = Math.pow(10,digits);
|
|
|
|
return Math.round(n*div)/div;
|
|
|
|
}
|
|
|
|
|
2023-10-09 13:03:12 -05:00
|
|
|
Number.hex = function(n)
|
|
|
|
{
|
|
|
|
var s = Math.floor(n).toString(16);
|
|
|
|
if (s.length === 1) s = '0' + s;
|
|
|
|
return s.uc();
|
|
|
|
}
|
|
|
|
|
2023-09-06 12:17:16 -05:00
|
|
|
Object.defineProperty(Object.prototype, 'lerp',{
|
|
|
|
value: function(to, t) {
|
2023-06-05 17:19:43 -05:00
|
|
|
var self = this;
|
|
|
|
var obj = {};
|
|
|
|
|
|
|
|
Object.keys(self).forEach(function(key) {
|
|
|
|
obj[key] = self[key].lerp(to[key],t);
|
|
|
|
});
|
|
|
|
|
|
|
|
return obj;
|
2023-09-06 12:17:16 -05:00
|
|
|
}});
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
/* MATH EXTENSIONS */
|
2023-06-05 17:19:43 -05:00
|
|
|
Object.defineProperty(Number.prototype, 'lerp', {
|
|
|
|
value: function(to, t) {
|
|
|
|
var s = this;
|
|
|
|
return (to - this) * t + this;
|
|
|
|
}
|
|
|
|
});
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
Math.clamp = function (x, l, h) { return x > h ? h : x < l ? l : x; }
|
|
|
|
|
2023-04-28 12:49:18 -05:00
|
|
|
Math.random_range = function(min,max) { return Math.random() * (max-min) + min; };
|
2023-12-28 17:38:17 -06:00
|
|
|
Math.rand_int = function(max) { return Math.floor(Math.random()*max); };
|
2023-04-28 12:49:18 -05:00
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
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) {
|
2023-12-26 15:39:46 -06:00
|
|
|
a1 = Math.turn2deg(a1);
|
|
|
|
a2 = Math.turn2deg(a2);
|
2023-04-22 16:44:26 -05:00
|
|
|
var dist = a2 - a1;
|
|
|
|
var wrap = dist >= 0 ? dist+360 : dist-360;
|
|
|
|
wrap %= 360;
|
|
|
|
|
|
|
|
if (Math.abs(dist) < Math.abs(wrap))
|
2023-12-26 15:39:46 -06:00
|
|
|
return Math.deg2turn(dist);
|
2023-04-22 16:44:26 -05:00
|
|
|
|
2023-12-26 15:39:46 -06:00
|
|
|
return Math.deg2turn(wrap);
|
2023-04-22 16:44:26 -05:00
|
|
|
};
|
2023-08-29 17:11:36 -05:00
|
|
|
Math.angledist.doc = "Find the shortest angle between two angles.";
|
2023-12-24 09:14:46 -06:00
|
|
|
Math.TAU = Math.PI*2;
|
2023-06-07 12:45:00 -05:00
|
|
|
Math.deg2rad = function(deg) { return deg * 0.0174533; };
|
|
|
|
Math.rad2deg = function(rad) { return rad / 0.0174533; };
|
2023-12-24 09:14:46 -06:00
|
|
|
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; };
|
2023-12-26 15:39:46 -06:00
|
|
|
Math.turn2deg = function(x) { return x*360; };
|
|
|
|
Math.deg2turn = function(x) { return x/360; };
|
2023-06-07 12:45:00 -05:00
|
|
|
Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * max), 0, max-1); };
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
/* BOUNDINGBOXES */
|
|
|
|
function cwh2bb(c, wh) {
|
|
|
|
return {
|
2023-11-08 01:39:10 -06:00
|
|
|
t: c.y+(wh.y/2),
|
|
|
|
b: c.y-(wh.y/2),
|
|
|
|
l: c.x-(wh.x/2),
|
|
|
|
r: c.x+(wh.x/2)
|
2023-04-22 16:44:26 -05:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
function points2bb(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;
|
|
|
|
};
|
|
|
|
|
2023-06-05 10:32:45 -05:00
|
|
|
function bb2points(bb)
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
[bb.l,bb.t],
|
|
|
|
[bb.r,bb.t],
|
|
|
|
[bb.r,bb.b],
|
|
|
|
[bb.l,bb.b]
|
|
|
|
];
|
|
|
|
}
|
2023-04-22 16:44:26 -05:00
|
|
|
|
2023-09-14 12:49:29 -05:00
|
|
|
function points2cwh(start,end)
|
|
|
|
{
|
|
|
|
var c = [];
|
|
|
|
c[0] = (end[0] - start[0]) / 2;
|
|
|
|
c[0] += start[0];
|
|
|
|
c[1] = (end[1] - start[1]) / 2;
|
|
|
|
c[1] += start[1];
|
|
|
|
var wh = [];
|
|
|
|
wh[0] = Math.abs(end[0] - start[0]);
|
|
|
|
wh[1] = Math.abs(end[1] - start[1]);
|
|
|
|
return {c: c, wh: wh};
|
|
|
|
}
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
function bb2cwh(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;
|
|
|
|
};
|
|
|
|
|
2023-06-05 10:32:45 -05:00
|
|
|
function pointinbb(bb, p)
|
|
|
|
{
|
|
|
|
if (bb.t < p.y || bb.b > p.y || bb.l > p.x || bb.r < p.x)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function movebb(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;
|
|
|
|
};
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
function bb_expand(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;
|
|
|
|
};
|
|
|
|
|
2023-10-04 17:57:37 -05:00
|
|
|
function bl2bb(bl, wh)
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
b: bl.y,
|
|
|
|
l: bl.x,
|
|
|
|
r: bl.x + wh.x,
|
|
|
|
t: bl.y + wh.y
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
function bb_from_objects(objs) {
|
|
|
|
var bb = objs[0].boundingbox;
|
|
|
|
objs.forEach(function(obj) { bb = bb_expand(bb, obj.boundingbox); });
|
|
|
|
return bb;
|
|
|
|
};
|
|
|
|
|
2023-10-04 08:18:09 -05:00
|
|
|
var Boundingbox = {};
|
|
|
|
Boundingbox.width = function(bb) { return bb.r - bb.l; };
|
|
|
|
Boundingbox.height = function(bb) { return bb.t - bb.b; };
|
|
|
|
Boundingbox.bl = function(bb) { return [bb.l, bb.b] };
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
/* VECTORS */
|
|
|
|
var Vector = {
|
|
|
|
length(v) {
|
|
|
|
var sum = v.reduce(function(acc, val) { return acc + val**2; }, 0);
|
|
|
|
return Math.sqrt(sum);
|
|
|
|
},
|
2023-12-19 17:28:45 -06:00
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
norm(v) {
|
|
|
|
var len = Vector.length(v);
|
|
|
|
return [v.x/len, v.y/len];
|
|
|
|
},
|
2023-12-19 17:28:45 -06:00
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
project(a, b) {
|
|
|
|
return cmd(85, a, b);
|
|
|
|
},
|
|
|
|
|
|
|
|
dot(a, b) {
|
2023-11-15 16:42:39 -06:00
|
|
|
return cmd(88,a,b);
|
2023-04-22 16:44:26 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
random() {
|
|
|
|
var vec = [Math.random()-0.5, Math.random()-0.5];
|
|
|
|
return Vector.norm(vec);
|
|
|
|
},
|
|
|
|
|
|
|
|
angle(v) {
|
2023-12-24 09:14:46 -06:00
|
|
|
return Math.rad2turn(Math.atan2(v.y, v.x));
|
2023-04-22 16:44:26 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
rotate(v,angle) {
|
|
|
|
var r = Vector.length(v);
|
2023-12-27 10:34:14 -06:00
|
|
|
angle += Vector.angle(v);
|
|
|
|
angle = Math.turn2rad(angle);
|
|
|
|
return [r*Math.cos(angle), r*Math.sin(angle)];
|
2023-04-22 16:44:26 -05:00
|
|
|
},
|
2023-10-26 11:48:02 -05:00
|
|
|
|
2023-04-22 16:44:26 -05:00
|
|
|
equal(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;
|
|
|
|
},
|
2023-11-15 16:42:39 -06:00
|
|
|
|
|
|
|
reflect(vec, plane) {
|
|
|
|
var p = Vector.norm(plane);
|
|
|
|
return vec.sub(p.scale(2*Vector.dot(vec, p)));
|
|
|
|
},
|
2023-12-20 17:20:29 -06:00
|
|
|
|
|
|
|
reflect_point(vec, point) {
|
|
|
|
return point.add(vec.sub(point).scale(-1));
|
|
|
|
},
|
2023-04-22 16:44:26 -05:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/* 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];
|
|
|
|
};
|
|
|
|
|
|
|
|
function sortpointsccw(points)
|
|
|
|
{
|
|
|
|
var cm = points2cm(points);
|
|
|
|
var cmpoints = points.map(function(x) { return x.sub(cm); });
|
|
|
|
var ccw = cmpoints.sort(function(a,b) {
|
|
|
|
aatan = Math.atan2(a.y, a.x);
|
|
|
|
batan = Math.atan2(b.y, b.x);
|
|
|
|
return aatan - batan;
|
|
|
|
});
|
|
|
|
|
|
|
|
return ccw.map(function(x) { return x.add(cm); });
|
|
|
|
}
|