prosperon/scripts/base.js

1148 lines
24 KiB
JavaScript

/* Removing crud I don't like */
/* Functions that lead to bloated error prone javascript */
/* 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;
Object.methods = function(o)
{
var m = [];
Object.keys(o).forEach(function(k) {
if (typeof o[k] === 'function') m.push(k);
});
return m;
}
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;
}
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.extend = function(from)
{
var n = {};
Object.mixin(n, from);
return n;
}
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 (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];
}
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.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 (Object.isAccessor(o2,k))
Object.defineProperty(o1, k, Object.getOwnPropertyDescriptor(o2,k));
else 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 */
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);
}
/* 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) {
Log.warn(`No property of name ${prop}.`);
return;
}
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) {
Log.warn(`No property of name ${prop}.`);
return;
}
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, '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, '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]];
},
});
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, 'remove', {
value: function(val) {
delete this[val.toString()];
}
});
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, 'fromlast', {
value: function(val) {
var idx = this.lastIndexOf(val);
if (idx === -1) return "";
return this.slice(idx+1);
}
});
Object.defineProperty(String.prototype, 'tolast', {
value: function(val) {
var idx = this.lastIndexOf(val);
if (idx === -1) return this.slice();
return this.slice(0,idx);
}
});
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+1);
}
});
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, 'base', {
value: function() { return this.fromlast('/'); }
});
Object.defineProperty(String.prototype, 'dir', {
value: function() { return this.tolast('/'); }
});
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, '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);
}
});
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, 'copy', {
value: function() {
var c = [];
this.forEach(function(x, i) {
c[i] = deep_copy(x);
});
return c;
}
});
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;
}
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 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];},
});
});
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, '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, '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.sort()) === JSON.stringify(b.sort());
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', {
value: function(fn) {
return this.map(x => fn(x));
}
});
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', {
get: 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; };
Number.prec = function(num)
{
return parseFloat(num.toFixed(3));
}
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;
}
});
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.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) {
var dist = a2 - a1;
var wrap = dist >= 0 ? dist+360 : dist-360;
wrap %= 360;
if (Math.abs(dist) < Math.abs(wrap))
return dist;
return wrap;
};
Math.angledist.doc = "Find the shortest angle between two angles.";
Math.patan2 = function(p) { return Math.atan2(p.y,p.x); };
Math.deg2rad = function(deg) { return deg * 0.0174533; };
Math.rad2deg = function(rad) { return rad / 0.0174533; };
Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * max), 0, max-1); };
/* BOUNDINGBOXES */
function cwh2bb(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)
};
};
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;
};
function bb2points(bb)
{
return [
[bb.l,bb.t],
[bb.r,bb.t],
[bb.r,bb.b],
[bb.l,bb.b]
];
}
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};
}
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;
};
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;
};
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;
};
function bl2bb(bl, wh)
{
return {
b: bl.y,
l: bl.x,
r: bl.x + wh.x,
t: bl.y + wh.y
};
}
function bb_from_objects(objs) {
var bb = objs[0].boundingbox;
objs.forEach(function(obj) { bb = bb_expand(bb, obj.boundingbox); });
return bb;
};
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] };
/* VECTORS */
var Vector = {
x: 0,
y: 0,
length(v) {
var sum = v.reduce(function(acc, val) { return acc + val**2; }, 0);
return Math.sqrt(sum);
},
norm(v) {
var len = Vector.length(v);
return [v.x/len, v.y/len];
},
make(x, y) {
var vec = Object.create(this, {x:x, y:y});
},
project(a, b) {
return cmd(85, a, b);
},
dot(a, b) {
},
random() {
var vec = [Math.random()-0.5, Math.random()-0.5];
return Vector.norm(vec);
},
angle(v) {
return Math.atan2(v.y, v.x);
},
rotate(v,angle) {
var r = Vector.length(v);
var p = Vector.angle(v) + angle;
return [r*Math.cos(p), r*Math.sin(p)];
},
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;
},
};
/* 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); });
}