// 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;