ootk
Version:
Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.
167 lines • 6.28 kB
JavaScript
/**
* @author @thkruz Theodore Kruczek
* @license AGPL-3.0-or-later
* @copyright (c) 2025 Kruczek Labs LLC
*
* Orbital Object ToolKit is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* Orbital Object ToolKit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* Orbital Object ToolKit. If not, see <http://www.gnu.org/licenses/>.
*/
import { Matrix, Vector, Vector3D, wrapAngle } from '../main.js';
export class Quaternion {
x;
y;
z;
w;
constructor(x, y, z, w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
static zero = new Quaternion(0, 0, 0, 0);
static one = new Quaternion(0, 0, 0, 1);
static xAxis = new Quaternion(1, 0, 0, 0);
static yAxis = new Quaternion(0, 1, 0, 0);
static zAxis = new Quaternion(0, 0, 1, 0);
toString(precision = 8) {
const xStr = this.x.toFixed(precision);
const yStr = this.y.toFixed(precision);
const zStr = this.z.toFixed(precision);
const wStr = this.w.toFixed(precision);
return `Q(x: ${xStr}, y: ${yStr}, z: ${zStr}, w: ${wStr})`;
}
positivePolar() {
return this.w >= 0 ? this.normalize() : this.negate().normalize();
}
magnitudeSquared() {
return this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z;
}
magnitude() {
return Math.sqrt(this.magnitudeSquared());
}
scale(n) {
return new Quaternion(n * this.x, n * this.y, n * this.z, n * this.w);
}
negate() {
return this.scale(-1);
}
normalize() {
const m = this.magnitude();
if (m === 0) {
return Quaternion.zero;
}
return this.scale(1 / m);
}
conjugate() {
return new Quaternion(-this.x, -this.y, -this.z, this.w);
}
inverse() {
return this.conjugate().scale(1 / this.magnitudeSquared());
}
add(q) {
return new Quaternion(this.x + q.x, this.y + q.y, this.z + q.z, this.w + q.w);
}
subtract(q) {
return new Quaternion(this.x - q.x, this.y - q.y, this.z - q.z, this.w - q.w);
}
addReal(n) {
return new Quaternion(this.x, this.y, this.z, this.w + n);
}
multiply(q) {
const mx = this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y;
const my = this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x;
const mz = this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w;
const mw = this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z;
return new Quaternion(mx, my, mz, mw);
}
dot(q) {
return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w;
}
rotateVector(v) {
const q = this.multiply(new Quaternion(v.x, v.y, v.z, 0)).multiply(this.conjugate());
return new Vector([q.x, q.y, q.z]);
}
rotateVector3D(v) {
const q = this.multiply(new Quaternion(v.x, v.y, v.z, 0)).multiply(this.conjugate());
return new Vector3D(q.x, q.y, q.z);
}
lerp(q, t) {
const f = 1.0 - t;
return new Quaternion(f * this.x + t * q.x, f * this.y + t * q.y, f * this.z + t * q.z, f * this.w + t * q.w).positivePolar();
}
slerp(q, t) {
let qp = q;
let dotP = this.dot(qp);
if (dotP < 0) {
dotP = -dotP;
qp = qp.negate();
}
if (dotP > 0.9995) {
return this.lerp(qp, t);
}
const theta = Math.acos(dotP);
const sinTheta = Math.sin(theta);
const f1 = Math.sin((1.0 - t) * theta) / sinTheta;
const f2 = Math.sin(t * theta) / sinTheta;
return new Quaternion(f1 * this.x + f2 * qp.x, f1 * this.y + f2 * qp.y, f1 * this.z + f2 * qp.z, f1 * this.w + f2 * qp.w).positivePolar();
}
toVector3D() {
return new Vector3D(this.x, this.y, this.z);
}
angle(q) {
const c = this.multiply(q.conjugate()).normalize();
return 2 * Math.atan2(c.toVector3D().magnitude(), c.w);
}
geodesicAngle(q) {
const p = this.dot(q);
return wrapAngle(Math.acos(2 * p * p - 1.0));
}
distance(q) {
const m01 = this.subtract(q).magnitude();
const p01 = this.add(q).magnitude();
return m01 < p01 ? m01 : p01;
}
delta(qTo) {
return this.inverse().multiply(qTo);
}
toDirectionCosineMatrix() {
const w2 = this.w * this.w;
const x2 = this.x * this.x;
const y2 = this.y * this.y;
const z2 = this.z * this.z;
const m = [
[w2 + x2 - y2 - z2, 2.0 * (this.x * this.y + this.z * this.w), 2.0 * (this.x * this.z - this.y * this.w)],
[2.0 * (this.x * this.y - this.z * this.w), w2 - x2 + y2 - z2, 2.0 * (this.y * this.z + this.x * this.w)],
[2.0 * (this.x * this.z + this.y * this.w), 2.0 * (this.y * this.z - this.x * this.w), w2 - x2 - y2 + z2],
];
return new Matrix(m);
}
toRotationMatrix() {
return this.toDirectionCosineMatrix().transpose();
}
vectorAngle(observer, target, forward) {
const delta = target.subtract(observer);
const transform = this.toDirectionCosineMatrix().multiplyVector3D(delta);
return forward.angle(transform);
}
kinematics(angularVelocity) {
const wPrime = new Vector([0, angularVelocity.x, angularVelocity.y, angularVelocity.z]);
const qMat = new Matrix([
[this.x, this.w, -this.z, this.y],
[this.y, this.z, this.w, -this.x],
[this.z, -this.y, this.x, this.w],
[this.w, -this.x, -this.y, -this.z],
]);
const result = qMat.multiplyVector(wPrime).scale(0.5).elements;
return new Quaternion(result[0], result[1], result[2], result[3]);
}
}
//# sourceMappingURL=Quaternion.js.map