import b2 from './bezier2'; import b3 from './bezier3'; import extG3 from './extG3'; import gauss from './gauss'; import GeometricE3 from './GeometricE3'; import GeometricNumber from './GeometricNumber'; import GeometricOperators from './GeometricOperators'; // import GradeError from './GradeError'; import ImmutableMeasure from './ImmutableMeasure'; import lcoG3 from './lcoG3'; import mulG3 from './mulG3'; import notImplemented from '../i18n/notImplemented'; import notSupported from '../i18n/notSupported'; import quadSpinorE3 from './quadSpinorE3' import readOnly from '../i18n/readOnly'; import rcoG3 from './rcoG3'; import scpG3 from './scpG3'; import SpinorE3 from './SpinorE3'; import squaredNormG3 from './squaredNormG3'; import stringFromCoordinates from './stringFromCoordinates'; import TrigMethods from './TrigMethods'; import {Unit} from './Unit'; import VectorE3 from './VectorE3'; import BASIS_LABELS_G3_GEOMETRIC from './BASIS_LABELS_G3_GEOMETRIC'; import BASIS_LABELS_G3_HAMILTON from './BASIS_LABELS_G3_HAMILTON'; import BASIS_LABELS_G3_STANDARD from './BASIS_LABELS_G3_STANDARD'; import BASIS_LABELS_G3_STANDARD_HTML from './BASIS_LABELS_G3_STANDARD_HTML'; const COORD_SCALAR = 0; const COORD_X = 1; const COORD_Y = 2; const COORD_Z = 3; const COORD_XY = 4; const COORD_YZ = 5; const COORD_ZX = 6; const COORD_PSEUDO = 7; /** *
* The G3 class represents a multivector for a 3-dimensional vector space with a Euclidean metric.
*
* The G3 class is immutable, making it easy to reason about values.
*
* The G3 class supports units of measures.
*
* The immutable nature of the G3 makes it less suitable for high performance graphics applications.
*
this + Iβ
*/
addPseudo(β: Unit): G3 {
return new G3(this.a, this.x, this.y, this.z, this.xy, this.yz, this.zx, this.b + β.multiplier, Unit.compatible(this.uom, β))
}
/**
* Computes this + α
*/
addScalar(α: Unit): G3 {
return new G3(this.a + α.multiplier, this.x, this.y, this.z, this.xy, this.yz, this.zx, this.b, Unit.compatible(this.uom, α))
}
__add__(rhs: Unit | G3): G3 {
if (rhs instanceof G3) {
return this.add(rhs);
}
else if (rhs instanceof Unit) {
return this.addScalar(rhs);
}
}
__radd__(lhs: Unit | G3): G3 {
if (lhs instanceof G3) {
return lhs.add(this)
}
else if (lhs instanceof Unit) {
return this.addScalar(lhs)
}
}
adj(): G3 {
throw new Error(notImplemented('adj').message)
}
/**
* @returns grade(log(this), 2)
*/
angle(): G3 {
return this.log().grade(2);
}
/**
* Computes the -1 * this
*/
neg() {
return new G3(-this.a, -this.x, -this.y, -this.z, -this.xy, -this.yz, -this.zx, -this.b, this.uom);
}
/**
* Unary minus (-).
*/
__neg__() {
return this.neg()
}
rev() {
return new G3(this.a, this.x, this.y, this.z, -this.xy, -this.yz, -this.zx, -this.b, this.uom);
}
/**
* ~ (tilde) produces reversion.
*/
__tilde__() {
return this.rev();
}
/**
* @param grade
*/
grade(grade: number) {
switch (grade) {
case 0:
return G3.fromCartesian(this.a, 0, 0, 0, 0, 0, 0, 0, this.uom);
case 1:
return G3.fromCartesian(0, this.x, this.y, this.z, 0, 0, 0, 0, this.uom);
case 2:
return G3.fromCartesian(0, 0, 0, 0, this.xy, this.yz, this.zx, 0, this.uom);
case 3:
return G3.fromCartesian(0, 0, 0, 0, 0, 0, 0, this.b, this.uom);
default:
return G3.fromCartesian(0, 0, 0, 0, 0, 0, 0, 0, this.uom);
}
}
cross(vector: G3) {
var x: number;
var x2: number;
var y: number;
var y1: number;
var y2: number;
var z: number;
var z1: number;
var z2: number;
const x1 = this.x;
y1 = this.y;
z1 = this.z;
x2 = vector.x;
y2 = vector.y;
z2 = vector.z;
x = y1 * z2 - z1 * y2;
y = z1 * x2 - x1 * z2;
z = x1 * y2 - y1 * x2;
return new G3(0, x, y, z, 0, 0, 0, 0, Unit.mul(this.uom, vector.uom));
}
isOne(): boolean {
return (this.a === 1) && (this.x === 0) && (this.y === 0) && (this.z === 0) && (this.yz === 0) && (this.zx === 0) && (this.xy === 0) && (this.b === 0);
}
isScalar(): boolean {
return (this.x === 0) && (this.y === 0) && (this.z === 0) && (this.yz === 0) && (this.zx === 0) && (this.xy === 0) && (this.b === 0);
}
isZero(): boolean {
return (this.a === 0) && (this.x === 0) && (this.y === 0) && (this.z === 0) && (this.yz === 0) && (this.zx === 0) && (this.xy === 0) && (this.b === 0);
}
lerp(target: G3, α: number): G3 {
throw new Error(notImplemented('lerp').message)
}
cos() {
// TODO: Generalize to full multivector.
Unit.assertDimensionless(this.uom)
const cosW = Math.cos(this.a)
return new G3(cosW, 0, 0, 0, 0, 0, 0, 0)
}
cosh(): G3 {
throw new Error(notImplemented('cosh').message)
}
distanceTo(point: G3): number {
// TODO: Should this be generalized to all coordinates?
const dx = this.x - point.x;
const dy = this.y - point.y;
const dz = this.z - point.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
equals(other: G3): boolean {
if (this.a === other.a && this.x === other.x && this.y === other.y && this.z === other.z && this.xy === other.xy && this.yz === other.yz && this.zx === other.zx && this.b === other.b) {
if (this.uom) {
if (other.uom) {
// TODO: We need equals on
return true
}
else {
return false
}
}
else {
if (other.uom) {
return false
}
else {
return true
}
}
}
else {
return false
}
}
exp() {
Unit.assertDimensionless(this.uom);
const bivector = this.grade(2);
const a = bivector.norm();
if (!a.isZero()) {
const c = a.cos();
const s = a.sin();
const B = bivector.direction();
return c.add(B.mul(s));
}
else {
return new G3(1, 0, 0, 0, 0, 0, 0, 0, this.uom);
}
}
/**
* Computes the inverse of this multivector, if it exists.
*/
inv() {
const α = this.a
const x = this.x
const y = this.y
const z = this.z
const xy = this.xy
const yz = this.yz
const zx = this.zx
const β = this.b
const A = [
[α, x, y, z, -xy, -yz, -zx, -β],
[x, α, xy, -zx, -y, -β, z, -yz],
[y, -xy, α, yz, x, -z, -β, -zx],
[z, zx, -yz, α, -β, y, -x, -xy],
[xy, -y, x, β, α, zx, -yz, z],
[yz, β, -z, y, -zx, α, xy, x],
[zx, z, β, -x, yz, -xy, α, y],
[β, yz, zx, xy, z, x, y, α]
]
const b = [1, 0, 0, 0, 0, 0, 0, 0]
const X = gauss(A, b)
const uom = this.uom ? this.uom.inv() : void 0
return new G3(X[0], X[1], X[2], X[3], X[4], X[5], X[6], X[7], uom);
}
/**
*
*/
log(): G3 {
throw new Error(notImplemented('log').message)
}
/**
* Computes the square root of the squared norm.
*/
magnitude(): G3 {
return this.norm();
}
/**
* Intentionally undocumented.
*/
magnitudeSansUnits(): number {
return Math.sqrt(this.squaredNormSansUnits())
}
/**
* Computes the magnitude of this G3. The magnitude is the square root of the quadrance.
*/
norm(): G3 {
return new G3(this.magnitudeSansUnits(), 0, 0, 0, 0, 0, 0, 0, this.uom)
}
/**
* Computes the quadrance of this G3. The quadrance is the square of the magnitude.
*/
quad(): G3 {
return this.squaredNorm();
}
/**
* @param t
* @param controlPoint
* @param endPoint
*/
quadraticBezier(t: number, controlPoint: GeometricE3, endPoint: GeometricE3) {
const x = b2(t, this.x, controlPoint.x, endPoint.x);
const y = b2(t, this.y, controlPoint.y, endPoint.y);
const z = b2(t, this.z, controlPoint.z, endPoint.z);
return new G3(0, x, y, z, 0, 0, 0, 0, this.uom);
}
squaredNorm() {
return new G3(this.squaredNormSansUnits(), 0, 0, 0, 0, 0, 0, 0, Unit.mul(this.uom, this.uom));
}
/**
* Intentionally undocumented
*/
squaredNormSansUnits(): number {
return squaredNormG3(this);
}
/**
* @param σ
*/
stress(σ: VectorE3): G3 {
throw new Error(notSupported('stress').message)
}
/**
* Computes the reflection of this multivector in the plane with normal n.
* @param n
*/
reflect(n: VectorE3) {
// TODO: Optimize to minimize object creation and increase performance.
let m = G3.fromVector(n)
return m.mul(this).mul(m).scale(-1)
}
rotate(R: SpinorE3) {
// FIXME: This only rotates the vector components.
// The units may be suspect to if rotate is not clearly defined.
const x = this.x;
const y = this.y;
const z = this.z;
const a = R.xy;
const b = R.yz;
const c = R.zx;
const α = R.a;
const quadR = quadSpinorE3(R)
const ix = α * x - c * z + a * y;
const iy = α * y - a * x + b * z;
const iz = α * z - b * y + c * x;
const iα = b * x + c * y + a * z;
const αOut = quadR * this.a
const xOut = ix * α + iα * b + iy * a - iz * c;
const yOut = iy * α + iα * c + iz * b - ix * a;
const zOut = iz * α + iα * a + ix * c - iy * b;
const βOut = quadR * this.b
return G3.fromCartesian(αOut, xOut, yOut, zOut, 0, 0, 0, βOut, this.uom)
}
sin() {
// TODO: Generalize to full multivector.
Unit.assertDimensionless(this.uom);
const sinW = Math.sin(this.a);
return new G3(sinW, 0, 0, 0, 0, 0, 0, 0, void 0);
}
sinh(): G3 {
throw new Error(notImplemented('sinh').message)
}
slerp(target: G3, α: number): G3 {
throw new Error(notImplemented('slerp').message)
}
sqrt() {
return new G3(Math.sqrt(this.a), 0, 0, 0, 0, 0, 0, 0, Unit.sqrt(this.uom));
}
tan(): G3 {
return this.sin().div(this.cos())
}
/**
* Intentionally undocumented.
*/
toStringCustom(coordToString: (x: number) => string, labels: (string | string[])[]): string {
const quantityString: string = stringFromCoordinates(this.coords, coordToString, labels);
if (this.uom) {
// Use the compact representation of the Unit because the units follow the multivector
// quantity and we want to suppress the multiplier which is always 1.
const unitString = this.uom.toString(10, true).trim();
if (unitString) {
return quantityString + ' ' + unitString;
}
else {
return quantityString;
}
}
else {
return quantityString;
}
}
toExponential(fractionDigits?: number): string {
const coordToString = function (coord: number): string { return coord.toExponential(fractionDigits) };
return this.toStringCustom(coordToString, G3.BASIS_LABELS);
}
toFixed(fractionDigits?: number): string {
const coordToString = function (coord: number): string { return coord.toFixed(fractionDigits) };
return this.toStringCustom(coordToString, G3.BASIS_LABELS);
}
toPrecision(precision?: number): string {
const coordToString = function (coord: number): string { return coord.toPrecision(precision) };
return this.toStringCustom(coordToString, G3.BASIS_LABELS);
}
toString(radix?: number): string {
const coordToString = function (coord: number): string { return coord.toString(radix) };
return this.toStringCustom(coordToString, G3.BASIS_LABELS);
}
__eq__(rhs: G3): boolean {
if (rhs instanceof G3) {
try {
Unit.compatible(this.uom, rhs.uom);
}
catch (e) {
throw new Error(`Dimensions mismatch in equality expression: ${this.uom.dimensions} === ${rhs.uom.dimensions}`);
}
return this.a === rhs.a &&
this.x === rhs.x &&
this.y === rhs.y &&
this.z === rhs.z &&
this.xy === rhs.xy &&
this.yz === rhs.yz &&
this.zx === rhs.zx &&
this.b === rhs.b;
}
else {
return void 0;
}
}
__ne__(rhs: G3): boolean {
if (rhs instanceof G3) {
try {
Unit.compatible(this.uom, rhs.uom);
}
catch (e) {
throw new Error(`Dimensions mismatch in inequality expression: ${this.uom.dimensions} !== ${rhs.uom.dimensions}`);
}
return this.a !== rhs.a ||
this.x !== rhs.x ||
this.y !== rhs.y ||
this.z !== rhs.z ||
this.xy !== rhs.xy ||
this.yz !== rhs.yz ||
this.zx !== rhs.zx ||
this.b !== this.b;
}
else {
return void 0;
}
}
__ge__(rhs: G3): boolean {
if (rhs instanceof G3) {
try {
Unit.compatible(this.uom, rhs.uom);
}
catch (e) {
throw new Error(`Dimensions mismatch in comparison expression: ${this.uom.dimensions} >= ${rhs.uom.dimensions}`);
}
if (!this.isScalar()) {
throw new Error(`left operand (${this}) in comparison expression must be a scalar.`);
}
if (!rhs.isScalar()) {
throw new Error(`right operand (${rhs}) in comparison expression must be a scalar.`);
}
return this.a >= rhs.a;
}
else {
return void 0;
}
}
__gt__(rhs: G3): boolean {
if (rhs instanceof G3) {
try {
Unit.compatible(this.uom, rhs.uom);
}
catch (e) {
throw new Error(`Dimensions mismatch in comparison expression: ${this.uom.dimensions} > ${rhs.uom.dimensions}`);
}
if (!this.isScalar()) {
throw new Error(`left operand (${this}) in comparison expression must be a scalar.`);
}
if (!rhs.isScalar()) {
throw new Error(`right operand (${rhs}) in comparison expression must be a scalar.`);
}
return this.a > rhs.a;
}
else {
return void 0;
}
}
__le__(rhs: G3): boolean {
if (rhs instanceof G3) {
try {
Unit.compatible(this.uom, rhs.uom);
}
catch (e) {
throw new Error(`Dimensions mismatch in comparison expression: ${this.uom.dimensions} <= ${rhs.uom.dimensions}`);
}
if (!this.isScalar()) {
throw new Error(`left operand (${this}) in comparison expression must be a scalar.`);
}
if (!rhs.isScalar()) {
throw new Error(`right operand (${rhs}) in comparison expression must be a scalar.`);
}
return this.a <= rhs.a;
}
else {
return void 0;
}
}
__lt__(rhs: G3): boolean {
if (rhs instanceof G3) {
try {
Unit.compatible(this.uom, rhs.uom);
}
catch (e) {
throw new Error(`Dimensions mismatch in comparison expression: ${this.uom.dimensions} < ${rhs.uom.dimensions}`);
}
if (!this.isScalar()) {
throw new Error(`left operand (${this}) in comparison expression must be a scalar.`);
}
if (!rhs.isScalar()) {
throw new Error(`right operand (${rhs}) in comparison expression must be a scalar.`);
}
return this.a < rhs.a;
}
else {
return void 0;
}
}
/**
* Provides access to the internals of G3 in order to use `product` functions.
*/
private static mutator(M: G3) {
const that: GeometricE3 = {
set a(value: number) {
M._coords[COORD_SCALAR] = value;
},
set x(value: number) {
M._coords[COORD_X] = value;
},
set y(value: number) {
M._coords[COORD_Y] = value;
},
set z(value: number) {
M._coords[COORD_Z] = value;
},
set yz(value: number) {
M._coords[COORD_YZ] = value;
},
set zx(value: number) {
M._coords[COORD_ZX] = value;
},
set xy(value: number) {
M._coords[COORD_XY] = value;
},
set b(value: number) {
M._coords[COORD_PSEUDO] = value;
}
}
return that
}
/**
* Creates a new G3 from the coordinates of m and the optional unit of measure, uom.
*/
static copy(m: GeometricE3, uom?: Unit): G3 {
return new G3(m.a, m.x, m.y, m.z, m.xy, m.yz, m.zx, m.b, uom)
}
static direction(vector: VectorE3): G3 {
if (vector) {
return new G3(0, vector.x, vector.y, vector.z, 0, 0, 0, 0).direction()
}
else {
return void 0
}
}
static fromSpinor(spinor: SpinorE3): G3 {
if (spinor) {
// FIXME: SpinorE3 should support uom, even though it might be 1
return new G3(spinor.a, 0, 0, 0, spinor.xy, spinor.yz, spinor.zx, 0, void 0)
}
else {
return void 0
}
}
static fromVector(vector: VectorE3, uom?: Unit): G3 {
if (vector) {
return new G3(0, vector.x, vector.y, vector.z, 0, 0, 0, 0, uom)
}
else {
return void 0
}
}
static random(uom?: Unit): G3 {
return new G3(Math.random(), Math.random(), Math.random(), Math.random(), Math.random(), Math.random(), Math.random(), Math.random(), uom)
}
static scalar(α: number, uom?: Unit): G3 {
return new G3(α, 0, 0, 0, 0, 0, 0, 0, uom)
}
static vector(x: number, y: number, z: number, uom?: Unit): G3 {
return new G3(0, x, y, z, 0, 0, 0, 0, uom)
}
}