// bluemath.js // This library adds advanced math objects function deg2rad (deg) { return ((Math.PI*2)/360)*deg; } function rad2deg (rad) { return (1/deg2rad(1))*rad; } class vector { constructor(dimentions=[0]) { var me = this; this.d = Array.isArray(dimentions) ? dimentions.map(d => !isNaN(d) ? d : 0) : [0]; if (me.d.length == 2) { this.getRadAngle = function (from) { from = from instanceof vector ? from : new vector([0,0]); return Math.atan2(me.d[1] - from.d[1], me.d[0] - from.d[0]); } this.getDegAngle = function (from) { return rad2deg(me.getRadAngle(from)); } this.resize = function (newLength) { var angle = me.getDegAngle(); var soh = Math.sin(angle) * newLength; var cah = Math.cos(angle) * newLength; return new vector([cah,soh]); } this.rotate = function (rad) { var theta = me.getRadAngle() + rad; var amp = me.getDistance(); return new vector([Math.cos(theta)*amp,Math.sin(theta)*amp]); } this.reflect = function (surfaceVector) { var theta = (2 * (surfaceVector.getDegAngle() + 90)) - me.getDegAngle(); var amp = me.getDistance(); return new vector([Math.cos(theta)*amp,Math.sin(theta)*amp]); } this.getPerp = function () { return me.rotate(deg2rad(90)); } this.slope = function () { return me.d[1] / me.d[0]; } this.yIntercept = function () { if (Math.abs(me.slope()) != Infinity) { return -(me.slope()*me.d[0])+me.d[1]; } else { return me.d[0]; } } this.xIntercept = function () { return -me.yIntercept()/me.slope(); } } } add(vec=new vector([...this.d.map(d => 0)])) { vec = vec instanceof vector ? vec : new vector([0]); var longest = vec.d.length >= this.d.length ? vec.d : this.d; var shortest = vec.d.length < this.d.length ? vec.d : this.d; return new vector([...longest.map(function (value,index) { var small = shortest[index]; if (!isNaN(small)) { return value + small; } else { return value; } })]); } mult(vec=new vector([...this.d.map(d => 1)])) { vec = vec instanceof vector ? vec : new vector([...this.d.map(d => 1)]); var longest = vec.d.length >= this.d.length ? vec.d : this.d; var shortest = vec.d.length < this.d.length ? vec.d : this.d; return new vector([...longest.map(function (value,index) { var small = shortest[index]; if (!isNaN(small)) { return value * small; } else { value; } })]); } mag(mult=1) { mult = !isNaN(mult) ? mult : 1; return this.mult(new vector([...this.d.map(d => mult)])); } square() { return this.mult(this); } getDistance(origin = new vector([...this.d.map(d => 0)])) { origin = origin instanceof vector ? origin : new vector([...this.d.map(d => 0)]); return Math.sqrt(this.d.map(function (value, index) { var ori = !isNaN(origin.d[index]) ? origin.d[index] : 0; return Math.pow(Math.abs(ori - value), 2); }).reduce((a,b) => a + b)); } } class lineSeg { constructor (pointA,pointB) { this.pointA = pointA; this.pointB = pointB; } intersect(line) { if (line instanceof lineSeg) { var x1 = this.pointA.d[0]; var x2 = this.pointB.d[0]; var x3 = line.pointA.d[0]; var x4 = line.pointB.d[0]; var y1 = this.pointA.d[1]; var y2 = this.pointB.d[1]; var y3 = line.pointA.d[1]; var y4 = line.pointB.d[1]; var deltaAX = x1 - x2; var deltaBX = x3 - x4; var deltaAY = y1 - y2; var deltaBY = y3 - y4; // I don't know wtf to call these var ground = deltaAX*deltaBY-deltaAY*deltaBX; var Across = x1*y2 - y1*x2; var Bcross = x3*y4 - y3*x4; var x = (Across*deltaBX-deltaAX*Bcross)/ground; var y = (Across*deltaBY-deltaAY*Bcross)/ground; if ( (new bound([x1],[x2])).inbounds([x]) && (new bound([x3],[x4])).inbounds([x]) && (new bound([y1],[y2])).inbounds([y]) && (new bound([y3],[y4])).inbounds([y])) { return new vector([x,y]); } else { return false; } } else { throw new TypeError("must check against line"); } } } function rad2vec (rad) { var x = Math.cos(rad); var y = Math.sin(rad); return new vector([x,y]); } function deg2vec (deg) { //abstraction, really just for sanity return rad2vec(deg2rad(deg)); } class bound { constructor (min=[-Infinity],max=[Infinity]) { // Failsafe if bare numbers are provided instead of an array // Insure both inputs are arrays with valid numbers min = Array.isArray(min) ? min.map(value => !isNaN(value) ? value : -Infinity) : ( !isNaN(min) ? [min] : [-Infinity] ); max = Array.isArray(max) ? max.map(value => !isNaN(value) ? value : -Infinity) : ( !isNaN(max) ? [max] : [Infinity] ); // Insure both arrays are equal in size while( min.length != max.length ) { if (min.length > max.length) { max.push(Infinity); } else { min.push(-Infinity); } } // Accept filtered bounds this.min = min; this.max = max; } inbounds(valueArray=[...this.min.map(v => 0)]) { if (valueArray.length != this.min.length) { return false } return valueArray.map((value,index) => (value >= this.min[index]) && (value <= this.max[index])).reduce((a,b) => a && b); } normalize(valueArray=[...this.min.map(v => 0)]) { if (valueArray.length != this.min.length) { return [...this.min.map(v => 0)]; } return valueArray.map((value,index) => (value - this.min[index])/(this.max[index] - this.min[index])); } clamp(valueArray=[...this.min.map(v => 0)]) { if (valueArray.length != this.min.length) { return [...this.min.map((v,i) => Math.min(Math.max(0, v), this.max[i]))]; } return valueArray.map((value,index) => Math.min(Math.max(value,this.min[index]),this.max[index])); } outOfBoundsBy(valueArray=[...this.min.map(v => 0)]) { if (this.inbounds(valueArray)) { return [...this.min.map(e=>0)]; } else { return this.min.map((value,index)=>{ if (undefined == valueArray[index]) { return NaN } if (valueArray[index] < value) { return -(value - valueArray[index]) } if (valueArray[index] > this.max[index]) { return valueArray[index] - this.max[index] } return 0; }); } } } class boundValue { constructor (bounds=new bound(),value=0) { this.bounds = bounds instanceof bound ? bounds : new bound(); this.value = !isNaN(value) ? value : 0; } setValue(value=0) { value = !isNaN(value) ? value : 0; return this.value = this.bounds.clamp([value])[0]; } getValue() { return this.bounds.clamp([this.value])[0]; } getValuePercent() { return this.bounds.normalize([this.value])[0]; } modValue(mod=0) { mod = !isNaN(mod) ? mod : 0; var outOfBoundsBy = this.bounds.outOfBoundsBy([this.value+mod]); return {newValue:this.setValue(this.value+mod),remainder:outOfBoundsBy}; } } function normalizeArray(array) { array = Array.isArray(array) ? array.map(e => !isNaN(e) ? e : 0) : [1]; var sum = array.reduce((a,b) => a+b); return array.map(v => v/sum); } class matrix { constructor (data) { var me = this; this.data = []; this.rows = 0; this.cols = 0; if (!Array.isArray(data)) { me.data.push([typeof data == "number" ? data : 0]); me.cols = me.data.length; me.rows = me.data[0].length; } else { me.cols = data.length; me.rows = data[0].length; for (var i = 0; i < me.cols; i++) { var thisCol = []; for (var j = 0; j < me.rows; j++) { thisCol.push(data[i][j]); } me.data.push(thisCol); } } this.getRow = function (row) { return me.data.map(function (col) { return col[row]; }); } this.addRow = function (rowData) { if (!Array.isArray(rowData)) { return console.error("Not provided an array: "+rowData); } me.data.forEach(function (col,i) { if (!(rowData[i])) { rowData[i] = 0; } col.push(rowData[i]); }); } this.getCol = function (col) { return Array.from(me.data[col]); } this.addCol = function (colData) { if (!Array.isArray(colData)) { return console.error("Not provided an array: "+colData); } colData = me.data[0].map(function (a,x) { if (colData[x]) { return colData[x]; } else { return 0;} }); me.data.push(colData); } this.transpose = function () { var resultCols = me.rows; var newData = []; for (var c = 0; c < resultCols; c++) { newData.push(me.getRow(c)); } return new matrix(newData); } this.multMat = function (matB) { if (!(matB instanceof matrix)) { return console.error("Must provide matrix for this method"); } if (me.cols != matB.rows) { return console.error("The number of columns in the first matrix should be equal to the number of rows in the second\n"+JSON.stringify(me.data)+" x "+JSON.stringify(matB.data)) } var resultRows = me.rows; var resultCols = matB.cols; var newData = []; for (var c = 0; c < resultCols; c++) { var bcol = matB.getCol(c); var thisCol = []; for (var r = 0; r < resultRows; r++) { thisCol.push(me.getRow(r).map(function (a,i) { var res = a * bcol[i]; return isNaN(res) ? 0 : res; }).reduce(function (a,b) { return a + b; }) ); } newData.push(thisCol); } return new matrix(newData); } this.multNum = function (num) { if (typeof num != "number") { return console.error("Must provide a number, provided "+(typeof num)); } return new matrix(me.data.map(function (a) { return a.map(function (b) { var res = b * num; return isNaN(res) ? 0 : res; }); })); } this.addMat = function (matB) { if (!(matB instanceof matrix)) { return console.error("Must provide matrix for this method"); } if (me.cols != matB.cols) {return console.error("Matrices must be of the same width"); } if (me.rows != matB.rows) {return console.error("Matrices must be of the same height"); } return new matrix(me.data.map(function (col,x) { return col.map(function (a,y) { var res = a + matB.data[x][y]; return isNaN(res) ? 0 : res; }); })); } } }