-
Notifications
You must be signed in to change notification settings - Fork 0
/
vector3d.js
252 lines (204 loc) · 7.69 KB
/
vector3d.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Vector handling functions (c) Chris Veness 2011-2016 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/geodesy/docs/module-vector3d.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
/**
* Library of 3-d vector manipulation routines.
*
* In a geodesy context, these vectors may be used to represent:
* - n-vector representing a normal to point on Earth's surface
* - earth-centered, earth fixed vector (≡ Gade’s ‘p-vector’)
* - great circle normal to vector (on spherical earth model)
* - motion vector on Earth's surface
* - etc
*
* Functions return vectors as return results, so that operations can be chained.
* @example var v = v1.cross(v2).dot(v3) // ≡ v1×v2⋅v3
*
* @module vector3d
*/
/**
* Creates a 3-d vector.
*
* The vector may be normalised, or use x/y/z values for eg height relative to the sphere or
* ellipsoid, distance from earth centre, etc.
*
* @constructor
* @param {number} x - X component of vector.
* @param {number} y - Y component of vector.
* @param {number} z - Z component of vector.
*/
function Vector3d(x, y, z) {
// allow instantiation without 'new'
if (!(this instanceof Vector3d)) return new Vector3d(x, y, z);
this.x = Number(x);
this.y = Number(y);
this.z = Number(z);
}
/**
* Adds supplied vector to ‘this’ vector.
*
* @param {Vector3d} v - Vector to be added to this vector.
* @returns {Vector3d} Vector representing sum of this and v.
*/
Vector3d.prototype.plus = function(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
return new Vector3d(this.x + v.x, this.y + v.y, this.z + v.z);
};
/**
* Subtracts supplied vector from ‘this’ vector.
*
* @param {Vector3d} v - Vector to be subtracted from this vector.
* @returns {Vector3d} Vector representing difference between this and v.
*/
Vector3d.prototype.minus = function(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
return new Vector3d(this.x - v.x, this.y - v.y, this.z - v.z);
};
/**
* Multiplies ‘this’ vector by a scalar value.
*
* @param {number} x - Factor to multiply this vector by.
* @returns {Vector3d} Vector scaled by x.
*/
Vector3d.prototype.times = function(x) {
x = Number(x);
return new Vector3d(this.x * x, this.y * x, this.z * x);
};
/**
* Divides ‘this’ vector by a scalar value.
*
* @param {number} x - Factor to divide this vector by.
* @returns {Vector3d} Vector divided by x.
*/
Vector3d.prototype.dividedBy = function(x) {
x = Number(x);
return new Vector3d(this.x / x, this.y / x, this.z / x);
};
/**
* Multiplies ‘this’ vector by the supplied vector using dot (scalar) product.
*
* @param {Vector3d} v - Vector to be dotted with this vector.
* @returns {number} Dot product of ‘this’ and v.
*/
Vector3d.prototype.dot = function(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
return this.x*v.x + this.y*v.y + this.z*v.z;
};
/**
* Multiplies ‘this’ vector by the supplied vector using cross (vector) product.
*
* @param {Vector3d} v - Vector to be crossed with this vector.
* @returns {Vector3d} Cross product of ‘this’ and v.
*/
Vector3d.prototype.cross = function(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
var x = this.y*v.z - this.z*v.y;
var y = this.z*v.x - this.x*v.z;
var z = this.x*v.y - this.y*v.x;
return new Vector3d(x, y, z);
};
/**
* Negates a vector to point in the opposite direction
*
* @returns {Vector3d} Negated vector.
*/
Vector3d.prototype.negate = function() {
return new Vector3d(-this.x, -this.y, -this.z);
};
/**
* Length (magnitude or norm) of ‘this’ vector
*
* @returns {number} Magnitude of this vector.
*/
Vector3d.prototype.length = function() {
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
};
/**
* Normalizes a vector to its unit vector
* – if the vector is already unit or is zero magnitude, this is a no-op.
*
* @returns {Vector3d} Normalised version of this vector.
*/
Vector3d.prototype.unit = function() {
var norm = this.length();
if (norm == 1) return this;
if (norm == 0) return this;
var x = this.x/norm;
var y = this.y/norm;
var z = this.z/norm;
return new Vector3d(x, y, z);
};
/**
* Calculates the angle between ‘this’ vector and supplied vector.
*
* @param {Vector3d} v
* @param {Vector3d} [n] - Plane normal: if supplied, angle is -π..+π, signed +ve if this->v is
* clockwise looking along n, -ve in opposite direction (if not supplied, angle is always 0..π).
* @returns {number} Angle (in radians) between this vector and supplied vector.
*/
Vector3d.prototype.angleTo = function(v, n) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
if (!(n instanceof Vector3d || n == undefined)) throw new TypeError('n is not Vector3d object');
var sign = n==undefined ? 1 : Math.sign(this.cross(v).dot(n));
var sinθ = this.cross(v).length() * sign;
var cosθ = this.dot(v);
return Math.atan2(sinθ, cosθ);
};
/**
* Rotates ‘this’ point around an axis by a specified angle.
*
* @param {Vector3d} axis - The axis being rotated around.
* @param {number} theta - The angle of rotation (in radians).
* @returns {Vector3d} The rotated point.
*/
Vector3d.prototype.rotateAround = function(axis, theta) {
if (!(axis instanceof Vector3d)) throw new TypeError('axis is not Vector3d object');
// en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
// en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
var p1 = this.unit();
var p = [ p1.x, p1.y, p1.z ]; // the point being rotated
var a = axis.unit(); // the axis being rotated around
var s = Math.sin(theta);
var c = Math.cos(theta);
// quaternion-derived rotation matrix
var q = [
[ a.x*a.x*(1-c) + c, a.x*a.y*(1-c) - a.z*s, a.x*a.z*(1-c) + a.y*s ],
[ a.y*a.x*(1-c) + a.z*s, a.y*a.y*(1-c) + c, a.y*a.z*(1-c) - a.x*s ],
[ a.z*a.x*(1-c) - a.y*s, a.z*a.y*(1-c) + a.x*s, a.z*a.z*(1-c) + c ],
];
// multiply q × p
var qp = [ 0, 0, 0 ];
for (var i=0; i<3; i++) {
for (var j=0; j<3; j++) {
qp[i] += q[i][j] * p[j];
}
}
var p2 = new Vector3d(qp[0], qp[1], qp[2]);
return p2;
// qv en.wikipedia.org/wiki/Rodrigues'_rotation_formula...
};
/**
* String representation of vector.
*
* @param {number} [precision=3] - Number of decimal places to be used.
* @returns {string} Vector represented as [x,y,z].
*/
Vector3d.prototype.toString = function(precision) {
var p = (precision === undefined) ? 3 : Number(precision);
var str = '[' + this.x.toFixed(p) + ',' + this.y.toFixed(p) + ',' + this.z.toFixed(p) + ']';
return str;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Polyfill Math.sign for old browsers / IE */
if (Math.sign === undefined) {
Math.sign = function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) return x;
return x > 0 ? 1 : -1;
};
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (typeof module != 'undefined' && module.exports) module.exports = Vector3d; // ≡ export default Vector3d