-
Notifications
You must be signed in to change notification settings - Fork 203
/
vector3d.js
256 lines (200 loc) · 8.08 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
253
254
255
256
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Vector handling functions (c) Chris Veness 2011-2019 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/geodesy-library.html#vector3d */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Library of 3-d vector manipulation routines.
*
* @module vector3d
*/
/* Vector3d - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Functions for manipulating generic 3-d vectors.
*
* Functions return vectors as return results, so that operations can be chained.
*
* @example
* const v = v1.cross(v2).dot(v3) // ≡ v1×v2⋅v3
*/
class Vector3d {
/**
* Creates a 3-d vector.
*
* @param {number} x - X component of vector.
* @param {number} y - Y component of vector.
* @param {number} z - Z component of vector.
*
* @example
* import Vector3d from '/js/geodesy/vector3d.js';
* const v = new Vector3d(0.267, 0.535, 0.802);
*/
constructor(x, y, z) {
if (isNaN(x) || isNaN(y) || isNaN(z)) throw new TypeError(`invalid vector [${x},${y},${z}]`);
this.x = Number(x);
this.y = Number(y);
this.z = Number(z);
}
/**
* Length (magnitude or norm) of ‘this’ vector.
*
* @returns {number} Magnitude of this vector.
*/
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.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.
*/
plus(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.
*/
minus(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.
*/
times(x) {
if (isNaN(x)) throw new TypeError(`invalid scalar value ‘${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.
*/
dividedBy(x) {
if (isNaN(x)) throw new TypeError(`invalid scalar value ‘${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.
*/
dot(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.
*/
cross(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
const x = this.y * v.z - this.z * v.y;
const y = this.z * v.x - this.x * v.z;
const 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.
*/
negate() {
return new Vector3d(-this.x, -this.y, -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.
*/
unit() {
const norm = this.length;
if (norm == 1) return this;
if (norm == 0) return this;
const x = this.x / norm;
const y = this.y / norm;
const z = this.z / norm;
return new Vector3d(x, y, z);
}
/**
* Calculates the angle between ‘this’ vector and supplied vector atan2(|p₁×p₂|, p₁·p₂) (or if
* (extra-planar) ‘n’ supplied then atan2(n·p₁×p₂, p₁·p₂).
*
* @param {Vector3d} v - Vector whose angle is to be determined from ‘this’ vector.
* @param {Vector3d} [n] - Plane normal: if supplied, angle is signed +ve if this->v is
* clockwise looking along n, -ve in opposite direction.
* @returns {number} Angle (in radians) between this vector and supplied vector (in range 0..π
* if n not supplied, range -π..+π if n supplied).
*/
angleTo(v, n=undefined) {
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');
// q.v. stackoverflow.com/questions/14066933#answer-16544330, but n·p₁×p₂ is numerically
// ill-conditioned, so just calculate sign to apply to |p₁×p₂|
// if n·p₁×p₂ is -ve, negate |p₁×p₂|
const sign = n==undefined || this.cross(v).dot(n)>=0 ? 1 : -1;
const sinθ = this.cross(v).length * sign;
const 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} angle - The angle of rotation (in degrees).
* @returns {Vector3d} The rotated point.
*/
rotateAround(axis, angle) {
if (!(axis instanceof Vector3d)) throw new TypeError('axis is not Vector3d object');
const θ = angle.toRadians();
// en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
// en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
const p = this.unit();
const a = axis.unit();
const s = Math.sin(θ);
const c = Math.cos(θ);
const t = 1-c;
const x = a.x, y = a.y, z = a.z;
const r = [ // rotation matrix for rotation about supplied axis
[ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
[ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
[ t*x*z - s*y, t*y*z + s*x, t*z*z + c ],
];
// multiply r × p
const rp = [
r[0][0]*p.x + r[0][1]*p.y + r[0][2]*p.z,
r[1][0]*p.x + r[1][1]*p.y + r[1][2]*p.z,
r[2][0]*p.x + r[2][1]*p.y + r[2][2]*p.z,
];
const p2 = new Vector3d(rp[0], rp[1], rp[2]);
return p2;
// qv en.wikipedia.org/wiki/Rodrigues'_rotation_formula...
}
/**
* String representation of vector.
*
* @param {number} [dp=3] - Number of decimal places to be used.
* @returns {string} Vector represented as [x,y,z].
*/
toString(dp=3) {
return `[${this.x.toFixed(dp)},${this.y.toFixed(dp)},${this.z.toFixed(dp)}]`;
}
}
// Extend Number object with methods to convert between degrees & radians
Number.prototype.toRadians = function() { return this * Math.PI / 180; };
Number.prototype.toDegrees = function() { return this * 180 / Math.PI; };
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
export default Vector3d;