439 lines
11 KiB
JavaScript
439 lines
11 KiB
JavaScript
|
// Copyright (c) 2011, Chris Umbel, James Coglan
|
||
|
// This file is required in order for any other classes to work. Some Vector methods work with the
|
||
|
// other Sylvester classes and are useless unless they are included. Other classes such as Line and
|
||
|
// Plane will not function at all without Vector being loaded first.
|
||
|
|
||
|
var Sylvester = require('./sylvester'),
|
||
|
Matrix = require('./matrix');
|
||
|
|
||
|
function Vector() {}
|
||
|
Vector.prototype = {
|
||
|
|
||
|
norm: function() {
|
||
|
var n = this.elements.length;
|
||
|
var sum = 0;
|
||
|
|
||
|
while (n--) {
|
||
|
sum += Math.pow(this.elements[n], 2);
|
||
|
}
|
||
|
|
||
|
return Math.sqrt(sum);
|
||
|
},
|
||
|
|
||
|
// Returns element i of the vector
|
||
|
e: function(i) {
|
||
|
return (i < 1 || i > this.elements.length) ? null : this.elements[i - 1];
|
||
|
},
|
||
|
|
||
|
// Returns the number of rows/columns the vector has
|
||
|
dimensions: function() {
|
||
|
return {rows: 1, cols: this.elements.length};
|
||
|
},
|
||
|
|
||
|
// Returns the number of rows in the vector
|
||
|
rows: function() {
|
||
|
return 1;
|
||
|
},
|
||
|
|
||
|
// Returns the number of columns in the vector
|
||
|
cols: function() {
|
||
|
return this.elements.length;
|
||
|
},
|
||
|
|
||
|
// Returns the modulus ('length') of the vector
|
||
|
modulus: function() {
|
||
|
return Math.sqrt(this.dot(this));
|
||
|
},
|
||
|
|
||
|
// Returns true iff the vector is equal to the argument
|
||
|
eql: function(vector) {
|
||
|
var n = this.elements.length;
|
||
|
var V = vector.elements || vector;
|
||
|
if (n != V.length) { return false; }
|
||
|
while (n--) {
|
||
|
if (Math.abs(this.elements[n] - V[n]) > Sylvester.precision) { return false; }
|
||
|
}
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
// Returns a copy of the vector
|
||
|
dup: function() {
|
||
|
return Vector.create(this.elements);
|
||
|
},
|
||
|
|
||
|
// Maps the vector to another vector according to the given function
|
||
|
map: function(fn) {
|
||
|
var elements = [];
|
||
|
this.each(function(x, i) {
|
||
|
elements.push(fn(x, i));
|
||
|
});
|
||
|
return Vector.create(elements);
|
||
|
},
|
||
|
|
||
|
// Calls the iterator for each element of the vector in turn
|
||
|
each: function(fn) {
|
||
|
var n = this.elements.length;
|
||
|
for (var i = 0; i < n; i++) {
|
||
|
fn(this.elements[i], i + 1);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Returns a new vector created by normalizing the receiver
|
||
|
toUnitVector: function() {
|
||
|
var r = this.modulus();
|
||
|
if (r === 0) { return this.dup(); }
|
||
|
return this.map(function(x) { return x / r; });
|
||
|
},
|
||
|
|
||
|
// Returns the angle between the vector and the argument (also a vector)
|
||
|
angleFrom: function(vector) {
|
||
|
var V = vector.elements || vector;
|
||
|
var n = this.elements.length, k = n, i;
|
||
|
if (n != V.length) { return null; }
|
||
|
var dot = 0, mod1 = 0, mod2 = 0;
|
||
|
// Work things out in parallel to save time
|
||
|
this.each(function(x, i) {
|
||
|
dot += x * V[i - 1];
|
||
|
mod1 += x * x;
|
||
|
mod2 += V[i - 1] * V[i - 1];
|
||
|
});
|
||
|
mod1 = Math.sqrt(mod1); mod2 = Math.sqrt(mod2);
|
||
|
if (mod1 * mod2 === 0) { return null; }
|
||
|
var theta = dot / (mod1 * mod2);
|
||
|
if (theta < -1) { theta = -1; }
|
||
|
if (theta > 1) { theta = 1; }
|
||
|
return Math.acos(theta);
|
||
|
},
|
||
|
|
||
|
// Returns true iff the vector is parallel to the argument
|
||
|
isParallelTo: function(vector) {
|
||
|
var angle = this.angleFrom(vector);
|
||
|
return (angle === null) ? null : (angle <= Sylvester.precision);
|
||
|
},
|
||
|
|
||
|
// Returns true iff the vector is antiparallel to the argument
|
||
|
isAntiparallelTo: function(vector) {
|
||
|
var angle = this.angleFrom(vector);
|
||
|
return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision);
|
||
|
},
|
||
|
|
||
|
// Returns true iff the vector is perpendicular to the argument
|
||
|
isPerpendicularTo: function(vector) {
|
||
|
var dot = this.dot(vector);
|
||
|
return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision);
|
||
|
},
|
||
|
|
||
|
// Returns the result of adding the argument to the vector
|
||
|
add: function(value) {
|
||
|
var V = value.elements || value;
|
||
|
|
||
|
if (this.elements.length != V.length)
|
||
|
return this.map(function(v) { return v + value });
|
||
|
else
|
||
|
return this.map(function(x, i) { return x + V[i - 1]; });
|
||
|
},
|
||
|
|
||
|
// Returns the result of subtracting the argument from the vector
|
||
|
subtract: function(v) {
|
||
|
if (typeof(v) == 'number')
|
||
|
return this.map(function(k) { return k - v; });
|
||
|
|
||
|
var V = v.elements || v;
|
||
|
if (this.elements.length != V.length) { return null; }
|
||
|
return this.map(function(x, i) { return x - V[i - 1]; });
|
||
|
},
|
||
|
|
||
|
// Returns the result of multiplying the elements of the vector by the argument
|
||
|
multiply: function(k) {
|
||
|
return this.map(function(x) { return x * k; });
|
||
|
},
|
||
|
|
||
|
elementMultiply: function(v) {
|
||
|
return this.map(function(k, i) {
|
||
|
return v.e(i) * k;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
sum: function() {
|
||
|
var sum = 0;
|
||
|
this.map(function(x) { sum += x;});
|
||
|
return sum;
|
||
|
},
|
||
|
|
||
|
chomp: function(n) {
|
||
|
var elements = [];
|
||
|
|
||
|
for (var i = n; i < this.elements.length; i++) {
|
||
|
elements.push(this.elements[i]);
|
||
|
}
|
||
|
|
||
|
return Vector.create(elements);
|
||
|
},
|
||
|
|
||
|
top: function(n) {
|
||
|
var elements = [];
|
||
|
|
||
|
for (var i = 0; i < n; i++) {
|
||
|
elements.push(this.elements[i]);
|
||
|
}
|
||
|
|
||
|
return Vector.create(elements);
|
||
|
},
|
||
|
|
||
|
augment: function(elements) {
|
||
|
var newElements = this.elements;
|
||
|
|
||
|
for (var i = 0; i < elements.length; i++) {
|
||
|
newElements.push(elements[i]);
|
||
|
}
|
||
|
|
||
|
return Vector.create(newElements);
|
||
|
},
|
||
|
|
||
|
x: function(k) { return this.multiply(k); },
|
||
|
|
||
|
log: function() {
|
||
|
return Vector.log(this);
|
||
|
},
|
||
|
|
||
|
elementDivide: function(vector) {
|
||
|
return this.map(function(v, i) {
|
||
|
return v / vector.e(i);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
product: function() {
|
||
|
var p = 1;
|
||
|
|
||
|
this.map(function(v) {
|
||
|
p *= v;
|
||
|
});
|
||
|
|
||
|
return p;
|
||
|
},
|
||
|
|
||
|
// Returns the scalar product of the vector with the argument
|
||
|
// Both vectors must have equal dimensionality
|
||
|
dot: function(vector) {
|
||
|
var V = vector.elements || vector;
|
||
|
var i, product = 0, n = this.elements.length;
|
||
|
if (n != V.length) { return null; }
|
||
|
while (n--) { product += this.elements[n] * V[n]; }
|
||
|
return product;
|
||
|
},
|
||
|
|
||
|
// Returns the vector product of the vector with the argument
|
||
|
// Both vectors must have dimensionality 3
|
||
|
cross: function(vector) {
|
||
|
var B = vector.elements || vector;
|
||
|
if (this.elements.length != 3 || B.length != 3) { return null; }
|
||
|
var A = this.elements;
|
||
|
return Vector.create([
|
||
|
(A[1] * B[2]) - (A[2] * B[1]),
|
||
|
(A[2] * B[0]) - (A[0] * B[2]),
|
||
|
(A[0] * B[1]) - (A[1] * B[0])
|
||
|
]);
|
||
|
},
|
||
|
|
||
|
// Returns the (absolute) largest element of the vector
|
||
|
max: function() {
|
||
|
var m = 0, i = this.elements.length;
|
||
|
while (i--) {
|
||
|
if (Math.abs(this.elements[i]) > Math.abs(m)) { m = this.elements[i]; }
|
||
|
}
|
||
|
return m;
|
||
|
},
|
||
|
|
||
|
|
||
|
maxIndex: function() {
|
||
|
var m = 0, i = this.elements.length;
|
||
|
var maxIndex = -1;
|
||
|
|
||
|
while (i--) {
|
||
|
if (Math.abs(this.elements[i]) > Math.abs(m)) {
|
||
|
m = this.elements[i];
|
||
|
maxIndex = i + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return maxIndex;
|
||
|
},
|
||
|
|
||
|
|
||
|
// Returns the index of the first match found
|
||
|
indexOf: function(x) {
|
||
|
var index = null, n = this.elements.length;
|
||
|
for (var i = 0; i < n; i++) {
|
||
|
if (index === null && this.elements[i] == x) {
|
||
|
index = i + 1;
|
||
|
}
|
||
|
}
|
||
|
return index;
|
||
|
},
|
||
|
|
||
|
// Returns a diagonal matrix with the vector's elements as its diagonal elements
|
||
|
toDiagonalMatrix: function() {
|
||
|
return Matrix.Diagonal(this.elements);
|
||
|
},
|
||
|
|
||
|
// Returns the result of rounding the elements of the vector
|
||
|
round: function() {
|
||
|
return this.map(function(x) { return Math.round(x); });
|
||
|
},
|
||
|
|
||
|
// Transpose a Vector, return a 1xn Matrix
|
||
|
transpose: function() {
|
||
|
var rows = this.elements.length;
|
||
|
var elements = [];
|
||
|
|
||
|
for (var i = 0; i < rows; i++) {
|
||
|
elements.push([this.elements[i]]);
|
||
|
}
|
||
|
return Matrix.create(elements);
|
||
|
},
|
||
|
|
||
|
// Returns a copy of the vector with elements set to the given value if they
|
||
|
// differ from it by less than Sylvester.precision
|
||
|
snapTo: function(x) {
|
||
|
return this.map(function(y) {
|
||
|
return (Math.abs(y - x) <= Sylvester.precision) ? x : y;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// Returns the vector's distance from the argument, when considered as a point in space
|
||
|
distanceFrom: function(obj) {
|
||
|
if (obj.anchor || (obj.start && obj.end)) { return obj.distanceFrom(this); }
|
||
|
var V = obj.elements || obj;
|
||
|
if (V.length != this.elements.length) { return null; }
|
||
|
var sum = 0, part;
|
||
|
this.each(function(x, i) {
|
||
|
part = x - V[i - 1];
|
||
|
sum += part * part;
|
||
|
});
|
||
|
return Math.sqrt(sum);
|
||
|
},
|
||
|
|
||
|
// Returns true if the vector is point on the given line
|
||
|
liesOn: function(line) {
|
||
|
return line.contains(this);
|
||
|
},
|
||
|
|
||
|
// Return true iff the vector is a point in the given plane
|
||
|
liesIn: function(plane) {
|
||
|
return plane.contains(this);
|
||
|
},
|
||
|
|
||
|
// Rotates the vector about the given object. The object should be a
|
||
|
// point if the vector is 2D, and a line if it is 3D. Be careful with line directions!
|
||
|
rotate: function(t, obj) {
|
||
|
var V, R = null, x, y, z;
|
||
|
if (t.determinant) { R = t.elements; }
|
||
|
switch (this.elements.length) {
|
||
|
case 2:
|
||
|
V = obj.elements || obj;
|
||
|
if (V.length != 2) { return null; }
|
||
|
if (!R) { R = Matrix.Rotation(t).elements; }
|
||
|
x = this.elements[0] - V[0];
|
||
|
y = this.elements[1] - V[1];
|
||
|
return Vector.create([
|
||
|
V[0] + R[0][0] * x + R[0][1] * y,
|
||
|
V[1] + R[1][0] * x + R[1][1] * y
|
||
|
]);
|
||
|
break;
|
||
|
case 3:
|
||
|
if (!obj.direction) { return null; }
|
||
|
var C = obj.pointClosestTo(this).elements;
|
||
|
if (!R) { R = Matrix.Rotation(t, obj.direction).elements; }
|
||
|
x = this.elements[0] - C[0];
|
||
|
y = this.elements[1] - C[1];
|
||
|
z = this.elements[2] - C[2];
|
||
|
return Vector.create([
|
||
|
C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z,
|
||
|
C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z,
|
||
|
C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z
|
||
|
]);
|
||
|
break;
|
||
|
default:
|
||
|
return null;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Returns the result of reflecting the point in the given point, line or plane
|
||
|
reflectionIn: function(obj) {
|
||
|
if (obj.anchor) {
|
||
|
// obj is a plane or line
|
||
|
var P = this.elements.slice();
|
||
|
var C = obj.pointClosestTo(P).elements;
|
||
|
return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
|
||
|
} else {
|
||
|
// obj is a point
|
||
|
var Q = obj.elements || obj;
|
||
|
if (this.elements.length != Q.length) { return null; }
|
||
|
return this.map(function(x, i) { return Q[i - 1] + (Q[i - 1] - x); });
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added
|
||
|
to3D: function() {
|
||
|
var V = this.dup();
|
||
|
switch (V.elements.length) {
|
||
|
case 3: break;
|
||
|
case 2: V.elements.push(0); break;
|
||
|
default: return null;
|
||
|
}
|
||
|
return V;
|
||
|
},
|
||
|
|
||
|
// Returns a string representation of the vector
|
||
|
inspect: function() {
|
||
|
return '[' + this.elements.join(', ') + ']';
|
||
|
},
|
||
|
|
||
|
// Set vector's elements from an array
|
||
|
setElements: function(els) {
|
||
|
this.elements = (els.elements || els).slice();
|
||
|
return this;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Constructor function
|
||
|
Vector.create = function(elements) {
|
||
|
var V = new Vector();
|
||
|
return V.setElements(elements);
|
||
|
};
|
||
|
|
||
|
// i, j, k unit vectors
|
||
|
Vector.i = Vector.create([1, 0, 0]);
|
||
|
Vector.j = Vector.create([0, 1, 0]);
|
||
|
Vector.k = Vector.create([0, 0, 1]);
|
||
|
|
||
|
// Random vector of size n
|
||
|
Vector.Random = function(n) {
|
||
|
var elements = [];
|
||
|
while (n--) { elements.push(Math.random()); }
|
||
|
return Vector.create(elements);
|
||
|
};
|
||
|
|
||
|
Vector.Fill = function(n, v) {
|
||
|
var elements = [];
|
||
|
while (n--) { elements.push(v); }
|
||
|
return Vector.create(elements);
|
||
|
};
|
||
|
|
||
|
// Vector filled with zeros
|
||
|
Vector.Zero = function(n) {
|
||
|
return Vector.Fill(n, 0);
|
||
|
};
|
||
|
|
||
|
Vector.One = function(n) {
|
||
|
return Vector.Fill(n, 1);
|
||
|
};
|
||
|
|
||
|
Vector.log = function(v) {
|
||
|
return v.map(function(x) {
|
||
|
return Math.log(x);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
module.exports = Vector;
|