322 lines
9.6 KiB
JavaScript
322 lines
9.6 KiB
JavaScript
// 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])[0];
|
|
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; }); }));
|
|
}
|
|
}
|
|
}
|