@g20/core
Version:
Geometric Algebra 2D Graphics Library
1,625 lines (1,591 loc) • 240 kB
JavaScript
/**
* @g20/core 1.0.0-alpha.46
* (c) David Geo Holmes david.geo.holmes@gmail.com
* Released under the MIT License.
*/
'use strict';
var reactive = require('@g20/reactive');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
function isFunction(value) {
return typeof value === 'function';
}
function createErrorClass(createImpl) {
var _super = function (instance) {
Error.call(instance);
instance.stack = new Error().stack;
};
var ctorFunc = createImpl(_super);
ctorFunc.prototype = Object.create(Error.prototype);
ctorFunc.prototype.constructor = ctorFunc;
return ctorFunc;
}
var UnsubscriptionError = createErrorClass(function (_super) {
return function UnsubscriptionErrorImpl(errors) {
_super(this);
this.message = errors
? errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ')
: '';
this.name = 'UnsubscriptionError';
this.errors = errors;
};
});
function arrRemove(arr, item) {
if (arr) {
var index = arr.indexOf(item);
0 <= index && arr.splice(index, 1);
}
}
var Subscription = (function () {
function Subscription(initialTeardown) {
this.initialTeardown = initialTeardown;
this.closed = false;
this._parentage = null;
this._finalizers = null;
}
Subscription.prototype.unsubscribe = function () {
var e_1, _a, e_2, _b;
var errors;
if (!this.closed) {
this.closed = true;
var _parentage = this._parentage;
if (_parentage) {
this._parentage = null;
if (Array.isArray(_parentage)) {
try {
for (var _parentage_1 = __values(_parentage), _parentage_1_1 = _parentage_1.next(); !_parentage_1_1.done; _parentage_1_1 = _parentage_1.next()) {
var parent_1 = _parentage_1_1.value;
parent_1.remove(this);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_parentage_1_1 && !_parentage_1_1.done && (_a = _parentage_1.return)) _a.call(_parentage_1);
}
finally { if (e_1) throw e_1.error; }
}
}
else {
_parentage.remove(this);
}
}
var initialFinalizer = this.initialTeardown;
if (isFunction(initialFinalizer)) {
try {
initialFinalizer();
}
catch (e) {
errors = e instanceof UnsubscriptionError ? e.errors : [e];
}
}
var _finalizers = this._finalizers;
if (_finalizers) {
this._finalizers = null;
try {
for (var _finalizers_1 = __values(_finalizers), _finalizers_1_1 = _finalizers_1.next(); !_finalizers_1_1.done; _finalizers_1_1 = _finalizers_1.next()) {
var finalizer = _finalizers_1_1.value;
try {
execFinalizer(finalizer);
}
catch (err) {
errors = errors !== null && errors !== void 0 ? errors : [];
if (err instanceof UnsubscriptionError) {
errors = __spreadArray(__spreadArray([], __read(errors)), __read(err.errors));
}
else {
errors.push(err);
}
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_finalizers_1_1 && !_finalizers_1_1.done && (_b = _finalizers_1.return)) _b.call(_finalizers_1);
}
finally { if (e_2) throw e_2.error; }
}
}
if (errors) {
throw new UnsubscriptionError(errors);
}
}
};
Subscription.prototype.add = function (teardown) {
var _a;
if (teardown && teardown !== this) {
if (this.closed) {
execFinalizer(teardown);
}
else {
if (teardown instanceof Subscription) {
if (teardown.closed || teardown._hasParent(this)) {
return;
}
teardown._addParent(this);
}
(this._finalizers = (_a = this._finalizers) !== null && _a !== void 0 ? _a : []).push(teardown);
}
}
};
Subscription.prototype._hasParent = function (parent) {
var _parentage = this._parentage;
return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));
};
Subscription.prototype._addParent = function (parent) {
var _parentage = this._parentage;
this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;
};
Subscription.prototype._removeParent = function (parent) {
var _parentage = this._parentage;
if (_parentage === parent) {
this._parentage = null;
}
else if (Array.isArray(_parentage)) {
arrRemove(_parentage, parent);
}
};
Subscription.prototype.remove = function (teardown) {
var _finalizers = this._finalizers;
_finalizers && arrRemove(_finalizers, teardown);
if (teardown instanceof Subscription) {
teardown._removeParent(this);
}
};
Subscription.EMPTY = (function () {
var empty = new Subscription();
empty.closed = true;
return empty;
})();
return Subscription;
}());
var EMPTY_SUBSCRIPTION = Subscription.EMPTY;
function isSubscription(value) {
return (value instanceof Subscription ||
(value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe)));
}
function execFinalizer(finalizer) {
if (isFunction(finalizer)) {
finalizer();
}
else {
finalizer.unsubscribe();
}
}
var config = {
onUnhandledError: null,
onStoppedNotification: null,
Promise: undefined,
useDeprecatedSynchronousErrorHandling: false,
useDeprecatedNextContext: false,
};
var timeoutProvider = {
setTimeout: function (handler, timeout) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
return setTimeout.apply(void 0, __spreadArray([handler, timeout], __read(args)));
},
clearTimeout: function (handle) {
return (clearTimeout)(handle);
},
delegate: undefined,
};
function reportUnhandledError(err) {
timeoutProvider.setTimeout(function () {
{
throw err;
}
});
}
function noop() { }
function errorContext(cb) {
{
cb();
}
}
var Subscriber = (function (_super) {
__extends(Subscriber, _super);
function Subscriber(destination) {
var _this = _super.call(this) || this;
_this.isStopped = false;
if (destination) {
_this.destination = destination;
if (isSubscription(destination)) {
destination.add(_this);
}
}
else {
_this.destination = EMPTY_OBSERVER;
}
return _this;
}
Subscriber.create = function (next, error, complete) {
return new SafeSubscriber(next, error, complete);
};
Subscriber.prototype.next = function (value) {
if (this.isStopped) ;
else {
this._next(value);
}
};
Subscriber.prototype.error = function (err) {
if (this.isStopped) ;
else {
this.isStopped = true;
this._error(err);
}
};
Subscriber.prototype.complete = function () {
if (this.isStopped) ;
else {
this.isStopped = true;
this._complete();
}
};
Subscriber.prototype.unsubscribe = function () {
if (!this.closed) {
this.isStopped = true;
_super.prototype.unsubscribe.call(this);
this.destination = null;
}
};
Subscriber.prototype._next = function (value) {
this.destination.next(value);
};
Subscriber.prototype._error = function (err) {
try {
this.destination.error(err);
}
finally {
this.unsubscribe();
}
};
Subscriber.prototype._complete = function () {
try {
this.destination.complete();
}
finally {
this.unsubscribe();
}
};
return Subscriber;
}(Subscription));
var _bind = Function.prototype.bind;
function bind(fn, thisArg) {
return _bind.call(fn, thisArg);
}
var ConsumerObserver = (function () {
function ConsumerObserver(partialObserver) {
this.partialObserver = partialObserver;
}
ConsumerObserver.prototype.next = function (value) {
var partialObserver = this.partialObserver;
if (partialObserver.next) {
try {
partialObserver.next(value);
}
catch (error) {
handleUnhandledError(error);
}
}
};
ConsumerObserver.prototype.error = function (err) {
var partialObserver = this.partialObserver;
if (partialObserver.error) {
try {
partialObserver.error(err);
}
catch (error) {
handleUnhandledError(error);
}
}
else {
handleUnhandledError(err);
}
};
ConsumerObserver.prototype.complete = function () {
var partialObserver = this.partialObserver;
if (partialObserver.complete) {
try {
partialObserver.complete();
}
catch (error) {
handleUnhandledError(error);
}
}
};
return ConsumerObserver;
}());
var SafeSubscriber = (function (_super) {
__extends(SafeSubscriber, _super);
function SafeSubscriber(observerOrNext, error, complete) {
var _this = _super.call(this) || this;
var partialObserver;
if (isFunction(observerOrNext) || !observerOrNext) {
partialObserver = {
next: (observerOrNext !== null && observerOrNext !== void 0 ? observerOrNext : undefined),
error: error !== null && error !== void 0 ? error : undefined,
complete: complete !== null && complete !== void 0 ? complete : undefined,
};
}
else {
var context_1;
if (_this && config.useDeprecatedNextContext) {
context_1 = Object.create(observerOrNext);
context_1.unsubscribe = function () { return _this.unsubscribe(); };
partialObserver = {
next: observerOrNext.next && bind(observerOrNext.next, context_1),
error: observerOrNext.error && bind(observerOrNext.error, context_1),
complete: observerOrNext.complete && bind(observerOrNext.complete, context_1),
};
}
else {
partialObserver = observerOrNext;
}
}
_this.destination = new ConsumerObserver(partialObserver);
return _this;
}
return SafeSubscriber;
}(Subscriber));
function handleUnhandledError(error) {
{
reportUnhandledError(error);
}
}
function defaultErrorHandler(err) {
throw err;
}
var EMPTY_OBSERVER = {
closed: true,
next: noop,
error: defaultErrorHandler,
complete: noop,
};
var observable = (function () { return (typeof Symbol === 'function' && Symbol.observable) || '@@observable'; })();
function identity(x) {
return x;
}
function pipeFromArray(fns) {
if (fns.length === 0) {
return identity;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input) {
return fns.reduce(function (prev, fn) { return fn(prev); }, input);
};
}
var Observable = (function () {
function Observable(subscribe) {
if (subscribe) {
this._subscribe = subscribe;
}
}
Observable.prototype.lift = function (operator) {
var observable = new Observable();
observable.source = this;
observable.operator = operator;
return observable;
};
Observable.prototype.subscribe = function (observerOrNext, error, complete) {
var _this = this;
var subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
errorContext(function () {
var _a = _this, operator = _a.operator, source = _a.source;
subscriber.add(operator
?
operator.call(subscriber, source)
: source
?
_this._subscribe(subscriber)
:
_this._trySubscribe(subscriber));
});
return subscriber;
};
Observable.prototype._trySubscribe = function (sink) {
try {
return this._subscribe(sink);
}
catch (err) {
sink.error(err);
}
};
Observable.prototype.forEach = function (next, promiseCtor) {
var _this = this;
promiseCtor = getPromiseCtor(promiseCtor);
return new promiseCtor(function (resolve, reject) {
var subscriber = new SafeSubscriber({
next: function (value) {
try {
next(value);
}
catch (err) {
reject(err);
subscriber.unsubscribe();
}
},
error: reject,
complete: resolve,
});
_this.subscribe(subscriber);
});
};
Observable.prototype._subscribe = function (subscriber) {
var _a;
return (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber);
};
Observable.prototype[observable] = function () {
return this;
};
Observable.prototype.pipe = function () {
var operations = [];
for (var _i = 0; _i < arguments.length; _i++) {
operations[_i] = arguments[_i];
}
return pipeFromArray(operations)(this);
};
Observable.prototype.toPromise = function (promiseCtor) {
var _this = this;
promiseCtor = getPromiseCtor(promiseCtor);
return new promiseCtor(function (resolve, reject) {
var value;
_this.subscribe(function (x) { return (value = x); }, function (err) { return reject(err); }, function () { return resolve(value); });
});
};
Observable.create = function (subscribe) {
return new Observable(subscribe);
};
return Observable;
}());
function getPromiseCtor(promiseCtor) {
var _a;
return (_a = promiseCtor !== null && promiseCtor !== void 0 ? promiseCtor : config.Promise) !== null && _a !== void 0 ? _a : Promise;
}
function isObserver(value) {
return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);
}
function isSubscriber(value) {
return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));
}
var ObjectUnsubscribedError = createErrorClass(function (_super) {
return function ObjectUnsubscribedErrorImpl() {
_super(this);
this.name = 'ObjectUnsubscribedError';
this.message = 'object unsubscribed';
};
});
var Subject = (function (_super) {
__extends(Subject, _super);
function Subject() {
var _this = _super.call(this) || this;
_this.closed = false;
_this.currentObservers = null;
_this.observers = [];
_this.isStopped = false;
_this.hasError = false;
_this.thrownError = null;
return _this;
}
Subject.prototype.lift = function (operator) {
var subject = new AnonymousSubject(this, this);
subject.operator = operator;
return subject;
};
Subject.prototype._throwIfClosed = function () {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
};
Subject.prototype.next = function (value) {
var _this = this;
errorContext(function () {
var e_1, _a;
_this._throwIfClosed();
if (!_this.isStopped) {
if (!_this.currentObservers) {
_this.currentObservers = Array.from(_this.observers);
}
try {
for (var _b = __values(_this.currentObservers), _c = _b.next(); !_c.done; _c = _b.next()) {
var observer = _c.value;
observer.next(value);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
}
});
};
Subject.prototype.error = function (err) {
var _this = this;
errorContext(function () {
_this._throwIfClosed();
if (!_this.isStopped) {
_this.hasError = _this.isStopped = true;
_this.thrownError = err;
var observers = _this.observers;
while (observers.length) {
observers.shift().error(err);
}
}
});
};
Subject.prototype.complete = function () {
var _this = this;
errorContext(function () {
_this._throwIfClosed();
if (!_this.isStopped) {
_this.isStopped = true;
var observers = _this.observers;
while (observers.length) {
observers.shift().complete();
}
}
});
};
Subject.prototype.unsubscribe = function () {
this.isStopped = this.closed = true;
this.observers = this.currentObservers = null;
};
Object.defineProperty(Subject.prototype, "observed", {
get: function () {
var _a;
return ((_a = this.observers) === null || _a === void 0 ? void 0 : _a.length) > 0;
},
enumerable: false,
configurable: true
});
Subject.prototype._trySubscribe = function (subscriber) {
this._throwIfClosed();
return _super.prototype._trySubscribe.call(this, subscriber);
};
Subject.prototype._subscribe = function (subscriber) {
this._throwIfClosed();
this._checkFinalizedStatuses(subscriber);
return this._innerSubscribe(subscriber);
};
Subject.prototype._innerSubscribe = function (subscriber) {
var _this = this;
var _a = this, hasError = _a.hasError, isStopped = _a.isStopped, observers = _a.observers;
if (hasError || isStopped) {
return EMPTY_SUBSCRIPTION;
}
this.currentObservers = null;
observers.push(subscriber);
return new Subscription(function () {
_this.currentObservers = null;
arrRemove(observers, subscriber);
});
};
Subject.prototype._checkFinalizedStatuses = function (subscriber) {
var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, isStopped = _a.isStopped;
if (hasError) {
subscriber.error(thrownError);
}
else if (isStopped) {
subscriber.complete();
}
};
Subject.prototype.asObservable = function () {
var observable = new Observable();
observable.source = this;
return observable;
};
Subject.create = function (destination, source) {
return new AnonymousSubject(destination, source);
};
return Subject;
}(Observable));
var AnonymousSubject = (function (_super) {
__extends(AnonymousSubject, _super);
function AnonymousSubject(destination, source) {
var _this = _super.call(this) || this;
_this.destination = destination;
_this.source = source;
return _this;
}
AnonymousSubject.prototype.next = function (value) {
var _a, _b;
(_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.next) === null || _b === void 0 ? void 0 : _b.call(_a, value);
};
AnonymousSubject.prototype.error = function (err) {
var _a, _b;
(_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.call(_a, err);
};
AnonymousSubject.prototype.complete = function () {
var _a, _b;
(_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.complete) === null || _b === void 0 ? void 0 : _b.call(_a);
};
AnonymousSubject.prototype._subscribe = function (subscriber) {
var _a, _b;
return (_b = (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber)) !== null && _b !== void 0 ? _b : EMPTY_SUBSCRIPTION;
};
return AnonymousSubject;
}(Subject));
var BehaviorSubject = (function (_super) {
__extends(BehaviorSubject, _super);
function BehaviorSubject(_value) {
var _this = _super.call(this) || this;
_this._value = _value;
return _this;
}
Object.defineProperty(BehaviorSubject.prototype, "value", {
get: function () {
return this.getValue();
},
enumerable: false,
configurable: true
});
BehaviorSubject.prototype._subscribe = function (subscriber) {
var subscription = _super.prototype._subscribe.call(this, subscriber);
!subscription.closed && subscriber.next(this._value);
return subscription;
};
BehaviorSubject.prototype.getValue = function () {
var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, _value = _a._value;
if (hasError) {
throw thrownError;
}
this._throwIfClosed();
return _value;
};
BehaviorSubject.prototype.next = function (value) {
_super.prototype.next.call(this, (this._value = value));
};
return BehaviorSubject;
}(Subject));
class DisposableObservable {
#rxjs;
constructor(rxjs) {
this.#rxjs = rxjs;
}
subscribe(callback) {
const subscription = this.#rxjs.subscribe(callback);
const disposable = {
dispose() {
subscription.unsubscribe();
}
};
return disposable;
}
}
class Variable {
#bs;
// readonly #options: VariableOptions<T>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(bs, options = {}) {
this.#bs = bs;
// this.#options = options;
}
get() {
return this.#bs.getValue();
}
set(newValue) {
// It appears that we need the first value for initialization.
this.#bs.next(newValue);
/*
const oldValue = this.#bs.getValue();
if (newValue !== oldValue) {
this.#bs.next(newValue);
}
else {
if (this.#attributes.equals) {
if (this.#attributes.equals(newValue, oldValue)) {
// Do nothing.
}
else {
this.#bs.next(newValue);
}
}
}
*/
}
asObservable() {
return new DisposableObservable(this.#bs.asObservable());
}
}
function variable(initialValue, options = {}) {
const bs = new BehaviorSubject(initialValue);
return new Variable(bs, options);
}
const abs = Math.abs;
/**
* @hidden
* @param n
* @param v
* @returns
*/
function makeColumnVector(n, v) {
const a = [];
for (let i = 0; i < n; i++) {
a.push(v);
}
return a;
}
/**
* @hidden
*/
function rowWithMaximumInColumn(A, column, N) {
let biggest = abs(A[column][column]);
let maxRow = column;
for (let row = column + 1; row < N; row++) {
if (abs(A[row][column]) > biggest) {
biggest = abs(A[row][column]);
maxRow = row;
}
}
return maxRow;
}
/**
* @hidden
*/
function swapRows(A, i, j, N) {
const colLength = N + 1;
for (let column = i; column < colLength; column++) {
const temp = A[j][column];
A[j][column] = A[i][column];
A[i][column] = temp;
}
}
/**
* @hidden
* @param A
* @param i
* @param N
*/
function makeZeroBelow(A, i, N) {
for (let row = i + 1; row < N; row++) {
const c = -A[row][i] / A[i][i];
for (let column = i; column < N + 1; column++) {
if (i === column) {
A[row][column] = 0;
}
else {
A[row][column] += c * A[i][column];
}
}
}
}
/**
* @hidden
* @param A
* @param N
* @returns
*/
function solve(A, N) {
const x = makeColumnVector(N, 0);
for (let i = N - 1; i > -1; i--) {
x[i] = A[i][N] / A[i][i];
for (let k = i - 1; k > -1; k--) {
A[k][N] -= A[k][i] * x[i];
}
}
return x;
}
/**
* Gaussian elimination
* Ax = b
* @hidden
*
* @param A an array containing the rows of A, where each row is a number array.
* @param b an array containing the rows of b, where each row is a number.
* @returns x an array containing the rows of x, where each row is a number.
*/
function gauss(A, b) {
const N = A.length;
for (let i = 0; i < N; i++) {
const Ai = A[i];
const bi = b[i];
Ai.push(bi);
}
for (let j = 0; j < N; j++) {
swapRows(A, j, rowWithMaximumInColumn(A, j, N), N);
makeZeroBelow(A, j, N);
}
return solve(A, N);
}
const sqrt = Math.sqrt;
/**
* Sets this multivector to a rotor representing a rotation from a to b.
* R = (|b||a| + b * a) / sqrt(2 * |b||a|(|b||a| + b << a))
* Returns undefined (void 0) if the vectors are anti-parallel.
*
* @param a The 'from' vector.
* @param b The 'to' vector.
* @param m The output multivector.
*/
function rotorFromDirections(a, b, m) {
const ax = a.x;
const ay = a.y;
const bx = b.x;
const by = b.y;
const aa = ax * ax + ay * ay;
const absA = sqrt(aa);
const bb = bx * bx + by * by;
const absB = sqrt(bb);
const BA = absB * absA;
const dotBA = ax * bx + ay * by;
const denom = sqrt(2 * (bb * aa + BA * dotBA));
if (denom !== 0) {
const B = ay * bx - ax * by;
m.set(0, 0, (BA + dotBA) / denom, B / denom);
}
}
function is_zero_vector(v) {
return v.x === 0 && v.y === 0;
}
function is_zero_bivector(m) {
return m.b === 0;
}
function is_zero_multivector(m) {
return is_zero_vector(m) && is_zero_bivector(m) && m.a === 0 && m.b === 0;
}
/**
* Sentinel value to indicate that the Geometric is not locked.
* UNLOCKED is in the range -1 to 0.
*
*/
const UNLOCKED = -1 * Math.random();
/**
* Sets the lock on the multivector argument and returns the same argument.
* This is a convenience function for the dunder (double underscore) methods.
* All dunder methods should return locked values.
*
*/
function lock(m) {
m.lock();
return m;
}
/**
*
*/
function isScalar(m) {
return m.x === 0 && m.y === 0 && m.b === 0;
}
function equalsValue$1(P, Q) {
return P[0] === Q[0] && P[1] === Q[1] && P[2] === Q[2] && P[3] === Q[3];
}
const COORD_A = 0;
const COORD_X = 1;
const COORD_Y = 2;
const COORD_B = 3;
function ensure_mutable(mv) {
if (mv.isMutable()) {
return mv;
}
else {
return mv.clone();
}
}
function vector_from_like(like) {
if (like instanceof G20) {
return ensure_mutable(like);
}
else if (Array.isArray(like)) {
return G20.vector(like[0], like[1]);
}
else {
return null;
}
}
function spinor_from_like(like) {
if (like instanceof G20) {
return ensure_mutable(like);
}
else if (Array.isArray(like)) {
return G20.spinor(like[0], like[1]);
}
else {
return null;
}
}
/**
* A multivector for two dimensions with a Euclidean metric.
*/
class G20 {
/**
* Contains the value that is currently stored in the signal (in the zeroth index of this array).
* This backing store exists because 1) the signal implementation we are using does not support
* mutation, and 2) we want this multivector to also be observable with events that only happen on
* changes, and 3) we want to avoid taxing the Garbage Collector.
*/
#signalValue = [
[0, 0, 0, 0],
[0, 0, 0, 0]
];
/**
* The underlying data that makes this multivector into a signal.
* The get method fo this signal MUST be called when accessing the coordinates (a, x, y, and b),
* and MUST NOT be called when mutating this multivector.
*/
#signal = reactive.signal(this.#signalValue[0], {
equals: equalsValue$1
});
#lock = UNLOCKED;
#change = variable(this);
change$ = this.#change.asObservable();
constructor(x = 0, y = 0, a = 0, b = 0) {
this.set(x, y, a, b);
}
static scalar(a) {
return new G20(0, 0, a, 0);
}
static bivector(b) {
return new G20(0, 0, 0, b);
}
static spinor(a, b) {
return new G20(0, 0, a, b);
}
static vector(x, y) {
return new G20(x, y, 0, 0);
}
/**
* Determines whether this multivector is locked.
* If the multivector is in the unlocked state then it is mutable.
* If the multivector is in the locked state then it is immutable.
*/
isLocked() {
return this.#lock !== UNLOCKED;
}
isMutable() {
return this.#lock === UNLOCKED;
}
/**
* Locks this multivector (preventing any further mutation),
* and returns a token that may be used to unlock it.
*/
lock() {
if (this.#lock !== UNLOCKED) {
throw new Error("already locked");
}
else {
this.#lock = Math.random();
return this.#lock;
}
}
/**
* Unlocks this multivector (allowing mutation),
* using a token that was obtained from a preceding lock method call.
*/
unlock(token) {
if (this.#lock === UNLOCKED) {
throw new Error("not locked");
}
else if (this.#lock === token) {
this.#lock = UNLOCKED;
return this;
}
else {
throw new Error("unlock denied");
}
}
get a() {
return this.#signal.get()[COORD_A];
}
set a(a) {
if (typeof a === "number") {
const coords = this.#signalValue[0];
const old_a = coords[COORD_A];
if (a !== old_a) {
const x = coords[COORD_X];
const y = coords[COORD_Y];
const b = coords[COORD_B];
this.set(x, y, a, b);
}
}
}
get x() {
return this.#signal.get()[COORD_X];
}
set x(x) {
if (typeof x === "number") {
const coords = this.#signalValue[0];
const old_x = coords[COORD_X];
if (x !== old_x) {
const a = coords[COORD_A];
const b = coords[COORD_B];
const y = coords[COORD_Y];
this.set(x, y, a, b);
}
}
}
get y() {
return this.#signal.get()[COORD_Y];
}
set y(y) {
if (typeof y === "number") {
const coords = this.#signalValue[0];
const old_y = coords[COORD_Y];
if (y !== old_y) {
const x = coords[COORD_X];
const a = coords[COORD_A];
const b = coords[COORD_B];
this.set(x, y, a, b);
}
}
}
get b() {
return this.#signal.get()[COORD_B];
}
set b(b) {
if (typeof b === "number") {
const coords = this.#signalValue[0];
const old_b = coords[COORD_B];
if (b !== old_b) {
const x = coords[COORD_X];
const y = coords[COORD_Y];
const a = coords[COORD_A];
this.set(x, y, a, b);
}
}
}
static one = lock(new G20(0, 0, 1, 0));
static zero = lock(new G20(0, 0, 0, 0));
static ex = lock(new G20(1, 0, 0, 0));
static ey = lock(new G20(0, 1, 0, 0));
static I = lock(new G20(0, 0, 0, 1));
static add(v1, v2) {
const x = v1.x + v2.x;
const y = v1.y + v2.y;
const a = v1.a + v2.a;
const b = v1.b + v2.b;
return new G20(x, y, a, b);
}
static copy(mv) {
return new G20(mv.x, mv.y, mv.a, mv.b);
}
static fromBivector(B) {
return G20.bivector(B.b);
}
static fromScalar(alpha) {
return G20.scalar(alpha.a);
}
static fromSpinor(R) {
return G20.spinor(R.a, R.b);
}
static fromVector(v) {
return G20.vector(v.x, v.y);
}
static rotorFromDirections(a, b) {
return new G20(0, 0, 0, 0).rotorFromDirections(a, b);
}
static rotorFromVectorToVector(a, b) {
return new G20(0, 0, 0, 0).rotorFromVectorToVector(a, b);
}
static sub(v1, v2) {
const x = v1.x - v2.x;
const y = v1.y - v2.y;
const a = v1.a - v2.a;
const b = v1.b - v2.b;
return new G20(x, y, a, b);
}
static subtract(v1, v2) {
return G20.sub(v1, v2);
}
static ratioBetween(v1, v2) {
return (v1.x * v2.x + v1.y * v2.y) / (v1.magnitude() * v2.magnitude());
}
static angleBetween(v1, v2) {
const dx = v1.x - v2.x;
const dy = v1.y - v2.y;
return Math.atan2(dy, dx);
}
static distanceBetween(v1, v2) {
return Math.sqrt(G20.distanceBetweenSquared(v1, v2));
}
static distanceBetweenSquared(v1, v2) {
const dx = v1.x - v2.x;
const dy = v1.y - v2.y;
return dx * dx + dy * dy;
}
/**
*
*/
add2(a, b) {
if (is_zero_multivector(a)) {
return this.set(b.x, b.y, b.a, b.b);
}
else if (is_zero_multivector(b)) {
return this.set(a.x, a.y, a.a, a.b);
}
else {
return this.set(a.x + b.x, a.y + b.y, a.a + b.a, a.b + b.b);
}
}
addPseudo(β) {
if (this.isLocked()) {
return lock(this.clone().addPseudo(β));
}
else {
if (β === 0) {
return this;
}
else {
return this.set(this.x, this.y, this.a, this.b + β);
}
}
}
/**
* Adds a multiple of a scalar to this multivector.
* @param a The scalar value to be added to this multivector.
* @param α The fraction of (a * uom) to be added. Default is 1.
* @returns this + (a * uom) * α
*/
addScalar(a, α = 1) {
if (this.isLocked()) {
return lock(this.clone().addScalar(a, α));
}
else {
if (this.isZero()) {
this.a = a * α;
return this;
}
else if (a === 0 || α === 0) {
return this;
}
else {
this.a += a * α;
return this;
}
}
}
conj() {
if (this.isLocked()) {
return lock(this.clone().conj());
}
else {
return this.set(-this.x, -this.y, this.a, -this.b);
}
}
/**
* A convenience function for set(mv.x, mv.y, mv.a, mv.b).
* Requires `this` multivector to be mutable.
*/
copy(mv) {
return this.set(mv.x, mv.y, mv.a, mv.b);
}
/**
* A convenience function for set(0, 0, spinor.a, spinor.b).
* Requires `this` multivector to be mutable.
*/
copySpinor(spinor) {
return this.set(0, 0, spinor.a, spinor.b);
}
/**
* A convenience function for set(vector.x, vector.y, 0, 0).
* Requires `this` multivector to be mutable.
*/
copyVector(vector) {
return this.set(vector.x, vector.y, 0, 0);
}
/**
* A convenience function for set(0, 0, 0, 0).
* Requires `this` multivector to be mutable.
*/
clear() {
return this.set(0, 0, 0, 0);
}
clone() {
return new G20(this.x, this.y, this.a, this.b);
}
/**
* @param rhs The multivector dividend.
* @returns this / m;
*/
div(rhs) {
if (this.isLocked()) {
return lock(this.clone().div(rhs));
}
else {
if (isScalar(rhs)) {
return this.scale(1 / rhs.a);
}
else {
return this.mul(G20.copy(rhs).inv());
}
}
}
/**
* @param m
* @returns this ^ m
*/
ext(m) {
if (this.isLocked()) {
return lock(this.clone().ext(m));
}
else {
const La = this.a;
const Lx = this.x;
const Ly = this.y;
const Lb = this.b;
const Ra = m.a;
const Rx = m.x;
const Ry = m.y;
const Rb = m.b;
const a = La * Ra;
const x = La * Rx + Lx * Ra;
const y = La * Ry + Ly * Ra;
const b = La * Rb + Lx * Ry - Ly * Rx + Lb * Ra;
return this.set(x, y, a, b);
}
}
/**
* Computes the right inverse of this multivector.
* inv(X) satisfies X * inv(X) = 1.
* @returns inverse(this)
*/
inv() {
if (this.isLocked()) {
return lock(this.clone().inv());
}
else {
const x0 = this.a;
const x1 = this.x;
const x2 = this.y;
const x3 = this.b;
const A = [
[+x0, +x1, +x2, -x3],
[+x1, +x0, -x3, +x2],
[+x2, +x3, +x0, -x1],
[+x3, +x2, -x1, +x0]
];
const s = [1, 0, 0, 0];
const X = gauss(A, s);
const a = X[0];
const x = X[1];
const y = X[2];
const b = X[3];
return this.set(x, y, a, b);
}
}
lco(rhs) {
if (this.isLocked()) {
return lock(this.clone().lco(rhs));
}
else {
return this.#lco2(this, rhs);
}
}
#lco2(lhs, rhs) {
const La = lhs.a;
const Lx = lhs.x;
const Ly = lhs.y;
const Lb = lhs.b;
const Ra = rhs.a;
const Rx = rhs.x;
const Ry = rhs.y;
const Rb = rhs.b;
const a = La * Ra + Lx * Rx + Ly * Ry - Lb * Rb;
const x = La * Rx - Ly * Rb;
const y = La * Ry + Lx * Rb;
const b = La * Rb;
return this.set(x, y, a, b);
}
add(rhs) {
if (this.isLocked()) {
return lock(this.clone().add(rhs));
}
else {
const x = this.x + rhs.x;
const y = this.y + rhs.y;
const a = this.a + rhs.a;
const b = this.b + rhs.b;
return this.set(x, y, a, b);
}
}
sub(rhs) {
if (this.isLocked()) {
return lock(this.clone().sub(rhs));
}
else {
const x = this.x - rhs.x;
const y = this.y - rhs.y;
const a = this.a - rhs.a;
const b = this.b - rhs.b;
return this.set(x, y, a, b);
}
}
/**
* @param rhs
* @returns this * m
*/
mul(rhs) {
if (this.isLocked()) {
return lock(this.clone().mul(rhs));
}
else {
return this.#mul2(this, rhs);
}
}
/**
*
*/
#mul2(lhs, rhs) {
const La = lhs.a;
const Lx = lhs.x;
const Ly = lhs.y;
const Lb = lhs.b;
const Ra = rhs.a;
const Rx = rhs.x;
const Ry = rhs.y;
const Rb = rhs.b;
const a = La * Ra + Lx * Rx + Ly * Ry - Lb * Rb;
const x = La * Rx + Lx * Ra - Ly * Rb + Lb * Ry;
const y = La * Ry + Lx * Rb + Ly * Ra - Lb * Rx;
const b = La * Rb + Lx * Ry - Ly * Rx + Lb * Ra;
return this.set(x, y, a, b);
}
neg() {
return this.scale(-1);
}
dot(v) {
return this.x * v.x + this.y * v.y;
}
exp() {
if (this.isLocked()) {
return lock(this.clone().exp());
}
else {
const w = this.a;
const z = this.b;
const expW = Math.exp(w);
// φ is actually the absolute value of one half the rotation angle.
// The orientation of the rotation gets carried in the bivector components.
const φ = Math.sqrt(z * z);
const s = expW * (φ !== 0 ? Math.sin(φ) / φ : 1);
const a = expW * Math.cos(φ);
const b = z * s;
return this.set(0, 0, a, b);
}
}
magnitude() {
return Math.sqrt(this.quaditude());
}
quaditude() {
const a = this.a;
const x = this.x;
const y = this.y;
const b = this.b;
return a * a + x * x + y * y - b * b;
}
normalize() {
if (this.isLocked()) {
return lock(this.clone().normalize());
}
else {
return this.scale(1 / this.magnitude());
}
}
distanceTo(v) {
return Math.sqrt(this.distanceToSquared(v));
}
distanceToSquared(v) {
const dx = this.x - v.x;
const dy = this.y - v.y;
return dx * dx + dy * dy;
}
rco(m) {
if (this.isLocked()) {
return lock(this.clone().rco(m));
}
else {
return this.#rco2(this, m);
}
}
#rco2(lhs, rhs) {
const La = lhs.a;
const Lx = lhs.x;
const Ly = lhs.y;
const Lb = lhs.b;
const Ra = rhs.a;
const Rx = rhs.x;
const Ry = rhs.y;
const Rb = rhs.b;
const a = La * Ra + Lx * Rx + Ly * Ry - Lb * Rb;
const x = Lx * Ra + Lb * Ry;
const y = Ly * Ra - Lb * Rx;
const b = Lb * Ra;
return this.set(x, y, a, b);
}
/**
* If `this` is mutable, then sets `this` multivector to its reflection in the plane orthogonal to vector n. The result is mutable.
* If `this` is immutable (locked), a copy of `this` is made, which is then reflected. The result is immutable (locked).
*
* i.e. The result is mutable (unlocked) iff `this` is mutable (unlocked).
*
* Mathematically,
*
* this ⟼ - n * this * n
*
* Geometrically,
*
* Reflects this multivector in the plane orthogonal to the unit vector, n.
* This implementation does assume that n is a vector, but does not assume that it is normalized to unity.
*
* If n is not a unit vector then the result is scaled by n squared.
* The scalar component gets an extra minus sign. The pseudoscalar component does not change sign.
* The units of measure are carried through but in most cases n SHOULD be dimensionless.
*
* @param n The unit vector that defines the reflection plane.
*/
reflect(n) {
if (this.isLocked()) {
return lock(this.clone().reflect(n));
}
else {
const nx = n.x;
const ny = n.y;
const a = this.a;
const x = this.x;
const y = this.y;
const b = this.b;
const nx2 = nx * nx;
const ny2 = ny * ny;
const μ = nx2 - ny2;
const λ = -2 * nx * ny;
const β = nx2 + ny2;
// The scalar component picks up a minus sign and the factor |n||n|.
const A = -β * a;
const X = λ * y - μ * x;
const Y = λ * x + μ * y;
// The pseudoscalar component does not change sign but still picks up the |n||n| factor.
const B = β * b;
// In most cases, n SHOULD be dimensionless.
return this.set(X, Y, A, B);
}
}
/**
* <p>
* Computes a rotor, R, from two unit vectors, where
* R = (|b||a| + b * a) / sqrt(2 * |b||a|(|b||a| + b << a))
* </p>
*
* The result is independent of the magnitudes of a and b.
*
* @param a The starting vector
* @param b The ending vector
* @returns The rotor representing a rotation from a to b.
*/
rotorFromDirections(a, b) {
if (this.isLocked()) {
return lock(this.clone().rotorFromDirections(a, b));
}
else {
rotorFromDirections(a, b, this);
return this;
}
}
/*
rotorFromFrameToFrame(es: Vector[], fs: Vector[]): G20 {
throw new Error(notImplemented('rotorFromFrameToFrame').message);
}
*/
/**
* Sets this multivector to a rotor that rotates through angle θ in the oriented plane defined by I.
*
* @param θ The rotation angle in radians when the rotor is applied on both sides as R * M * ~R
*/
rotorFromAngle(θ) {
if (this.isLocked()) {
return lock(this.clone().rotorFromAngle(θ));
}
else {
const φ = θ / 2;
return this.set(0, 0, Math.cos(φ), -Math.sin(φ));
}
}
/**
* R = sqrt(|b|/|a|) * (|b||a| + b * a) / sqrt(2 * |b||a|(|b||a| + b << a))
*
* The result is depends on the magnitudes of a and b.
*/
rotorFromVectorToVector(a, b) {
if (this.isLocked()) {
return lock(this.clone().rotorFromVectorToVector(a, b));
}
else {
const ax = a.x;
const ay = a.y;
const bx = b.x;
const by = b.y;
const absB = Math.sqrt(bx * bx + by * by);
const absA = Math.sqrt(ax * ax + ay * ay);
const BA = absB * absA;
const dotBA = bx * ax + by * ay;
/**
* q = b ^ a