kld-affine
Version:
A collection of classes used in affine geometry
621 lines (559 loc) • 14.3 kB
JavaScript
/**
* Matrix2D.js
* @module Matrix2D
* @copyright 2001-2019 Kevin Lindsey
*/
/**
* Matrix2D
*
* @memberof module:kld-affine
*/
class Matrix2D {
/**
* A 2D Matrix of the form:<br>
* [a c e]<br>
* [b d f]<br>
* [0 0 1]<br>
*
* @param {number} a
* @param {number} b
* @param {number} c
* @param {number} d
* @param {number} e
* @param {number} f
* @returns {module:kld-affine.Matrix2D}
*/
constructor(a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
}
/**
* translation
*
* @param {number} tx
* @param {number} ty
* @returns {module:kld-affine.Matrix2D}
*/
static translation(tx, ty) {
return new Matrix2D(1, 0, 0, 1, tx, ty);
}
/**
* scaling
*
* @param {number} scale
* @returns {module:kld-affine.Matrix2D}
*/
static scaling(scale) {
return new Matrix2D(scale, 0, 0, scale, 0, 0);
}
/**
* scalingAt
*
* @param {number} scale
* @param {module:kld-affine.Point2D} center
* @returns {module:kld-affine.Matrix2D}
*/
static scalingAt(scale, center) {
return new Matrix2D(
scale,
0,
0,
scale,
center.x - center.x * scale,
center.y - center.y * scale
);
}
/**
* nonUniformScaling
*
* @param {number} scaleX
* @param {number} scaleY
* @returns {module:kld-affine.Matrix2D}
*/
static nonUniformScaling(scaleX, scaleY) {
return new Matrix2D(scaleX, 0, 0, scaleY, 0, 0);
}
/**
* nonUniformScalingAt
*
* @param {number} scaleX
* @param {number} scaleY
* @param {module:kld-affine.Point2D} center
* @returns {module:kld-affine.Matrix2D}
*/
static nonUniformScalingAt(scaleX, scaleY, center) {
return new Matrix2D(
scaleX,
0,
0,
scaleY,
center.x - center.x * scaleX,
center.y - center.y * scaleY
);
}
/**
* rotation
*
* @param {number} radians
* @returns {module:kld-affine.Matrix2D}
*/
static rotation(radians) {
const c = Math.cos(radians);
const s = Math.sin(radians);
return new Matrix2D(c, s, -s, c, 0, 0);
}
/**
* rotationAt
*
* @param {number} radians
* @param {module:kld-affine.Point2D} center
* @returns {module:kld-affine.Matrix2D}
*/
static rotationAt(radians, center) {
const c = Math.cos(radians);
const s = Math.sin(radians);
return new Matrix2D(
c,
s,
-s,
c,
center.x - center.x * c + center.y * s,
center.y - center.y * c - center.x * s
);
}
/**
* rotationFromVector
*
* @param {module:kld-affine.Vector2D} vector
* @returns {module:kld-affine.Matrix2D}
*/
static rotationFromVector(vector) {
const unit = vector.unit();
const c = unit.x; // cos
const s = unit.y; // sin
return new Matrix2D(c, s, -s, c, 0, 0);
}
/**
* xFlip
*
* @returns {module:kld-affine.Matrix2D}
*/
static xFlip() {
return new Matrix2D(-1, 0, 0, 1, 0, 0);
}
/**
* yFlip
*
* @returns {module:kld-affine.Matrix2D}
*/
static yFlip() {
return new Matrix2D(1, 0, 0, -1, 0, 0);
}
/**
* xSkew
*
* @param {number} radians
* @returns {module:kld-affine.Matrix2D}
*/
static xSkew(radians) {
const t = Math.tan(radians);
return new Matrix2D(1, 0, t, 1, 0, 0);
}
/**
* ySkew
*
* @param {number} radians
* @returns {module:kld-affine.Matrix2D}
*/
static ySkew(radians) {
const t = Math.tan(radians);
return new Matrix2D(1, t, 0, 1, 0, 0);
}
/**
* multiply
*
* @param {module:kld-affine.Matrix2D} that
* @returns {module:kld-affine.Matrix2D}
*/
multiply(that) {
if (this.isIdentity()) {
return that;
}
if (that.isIdentity()) {
return this;
}
return new this.constructor(
this.a * that.a + this.c * that.b,
this.b * that.a + this.d * that.b,
this.a * that.c + this.c * that.d,
this.b * that.c + this.d * that.d,
this.a * that.e + this.c * that.f + this.e,
this.b * that.e + this.d * that.f + this.f
);
}
/**
* inverse
*
* @returns {module:kld-affine.Matrix2D}
*/
inverse() {
if (this.isIdentity()) {
return this;
}
const det1 = this.a * this.d - this.b * this.c;
if (det1 === 0.0) {
throw new Error("Matrix is not invertible");
}
const idet = 1.0 / det1;
const det2 = this.f * this.c - this.e * this.d;
const det3 = this.e * this.b - this.f * this.a;
return new this.constructor(
this.d * idet,
-this.b * idet,
-this.c * idet,
this.a * idet,
det2 * idet,
det3 * idet
);
}
/**
* translate
*
* @param {number} tx
* @param {number} ty
* @returns {module:kld-affine.Matrix2D}
*/
translate(tx, ty) {
return new this.constructor(
this.a,
this.b,
this.c,
this.d,
this.a * tx + this.c * ty + this.e,
this.b * tx + this.d * ty + this.f
);
}
/**
* scale
*
* @param {number} scale
* @returns {module:kld-affine.Matrix2D}
*/
scale(scale) {
return new this.constructor(
this.a * scale,
this.b * scale,
this.c * scale,
this.d * scale,
this.e,
this.f
);
}
/**
* scaleAt
*
* @param {number} scale
* @param {module:kld-affine.Point2D} center
* @returns {module:kld-affine.Matrix2D}
*/
scaleAt(scale, center) {
const dx = center.x - scale * center.x;
const dy = center.y - scale * center.y;
return new this.constructor(
this.a * scale,
this.b * scale,
this.c * scale,
this.d * scale,
this.a * dx + this.c * dy + this.e,
this.b * dx + this.d * dy + this.f
);
}
/**
* scaleNonUniform
*
* @param {number} scaleX
* @param {number} scaleY
* @returns {module:kld-affine.Matrix2D}
*/
scaleNonUniform(scaleX, scaleY) {
return new this.constructor(
this.a * scaleX,
this.b * scaleX,
this.c * scaleY,
this.d * scaleY,
this.e,
this.f
);
}
/**
* scaleNonUniformAt
*
* @param {number} scaleX
* @param {number} scaleY
* @param {module:kld-affine.Point2D} center
* @returns {module:kld-affine.Matrix2D}
*/
scaleNonUniformAt(scaleX, scaleY, center) {
const dx = center.x - scaleX * center.x;
const dy = center.y - scaleY * center.y;
return new this.constructor(
this.a * scaleX,
this.b * scaleX,
this.c * scaleY,
this.d * scaleY,
this.a * dx + this.c * dy + this.e,
this.b * dx + this.d * dy + this.f
);
}
/**
* rotate
*
* @param {number} radians
* @returns {module:kld-affine.Matrix2D}
*/
rotate(radians) {
const c = Math.cos(radians);
const s = Math.sin(radians);
return new this.constructor(
this.a * c + this.c * s,
this.b * c + this.d * s,
this.a * -s + this.c * c,
this.b * -s + this.d * c,
this.e,
this.f
);
}
/**
* rotateAt
*
* @param {number} radians
* @param {module:kld-affine.Point2D} center
* @returns {module:kld-affine.Matrix2D}
*/
rotateAt(radians, center) {
const cos = Math.cos(radians);
const sin = Math.sin(radians);
const cx = center.x;
const cy = center.y;
const a = this.a * cos + this.c * sin;
const b = this.b * cos + this.d * sin;
const c = this.c * cos - this.a * sin;
const d = this.d * cos - this.b * sin;
return new this.constructor(
a,
b,
c,
d,
(this.a - a) * cx + (this.c - c) * cy + this.e,
(this.b - b) * cx + (this.d - d) * cy + this.f
);
}
/**
* rotateFromVector
*
* @param {module:kld-affine.Vector2D} vector
* @returns {module:kld-affine.Matrix2D}
*/
rotateFromVector(vector) {
const unit = vector.unit();
const c = unit.x; // cos
const s = unit.y; // sin
return new this.constructor(
this.a * c + this.c * s,
this.b * c + this.d * s,
this.a * -s + this.c * c,
this.b * -s + this.d * c,
this.e,
this.f
);
}
/**
* flipX
*
* @returns {module:kld-affine.Matrix2D}
*/
flipX() {
return new this.constructor(
-this.a,
-this.b,
this.c,
this.d,
this.e,
this.f
);
}
/**
* flipY
*
* @returns {module:kld-affine.Matrix2D}
*/
flipY() {
return new this.constructor(
this.a,
this.b,
-this.c,
-this.d,
this.e,
this.f
);
}
/**
* skewX
*
* @param {number} radians
* @returns {module:kld-affine.Matrix2D}
*/
skewX(radians) {
const t = Math.tan(radians);
return new this.constructor(
this.a,
this.b,
this.c + this.a * t,
this.d + this.b * t,
this.e,
this.f
);
}
// TODO: skewXAt
/**
* skewY
*
* @param {number} radians
* @returns {module:kld-affine.Matrix2D}
*/
skewY(radians) {
const t = Math.tan(radians);
return new this.constructor(
this.a + this.c * t,
this.b + this.d * t,
this.c,
this.d,
this.e,
this.f
);
}
// TODO: skewYAt
/**
* isIdentity
*
* @returns {boolean}
*/
isIdentity() {
return (
this.a === 1.0 &&
this.b === 0.0 &&
this.c === 0.0 &&
this.d === 1.0 &&
this.e === 0.0 &&
this.f === 0.0
);
}
/**
* isInvertible
*
* @returns {boolean}
*/
isInvertible() {
return this.a * this.d - this.b * this.c !== 0.0;
}
/**
* getScale
*
* @returns {{ scaleX: number, scaleY: number }}
*/
getScale() {
return {
scaleX: Math.sqrt(this.a * this.a + this.c * this.c),
scaleY: Math.sqrt(this.b * this.b + this.d * this.d)
};
}
/**
* Calculates matrix Singular Value Decomposition
*
* The resulting matrices — translation, rotation, scale, and rotation0 — return
* this matrix when they are multiplied together in the listed order
*
* @see Jim Blinn's article {@link http://dx.doi.org/10.1109/38.486688}
* @see {@link http://math.stackexchange.com/questions/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation}
*
* @returns {{
* translation: module:kld-affine.Matrix2D,
* rotation: module:kld-affine.Matrix2D,
* scale: module:kld-affine.Matrix2D,
* rotation0: module:kld-affine.Matrix2D
* }}
*/
getDecomposition() {
const E = (this.a + this.d) * 0.5;
const F = (this.a - this.d) * 0.5;
const G = (this.b + this.c) * 0.5;
const H = (this.b - this.c) * 0.5;
const Q = Math.sqrt(E * E + H * H);
const R = Math.sqrt(F * F + G * G);
const scaleX = Q + R;
const scaleY = Q - R;
const a1 = Math.atan2(G, F);
const a2 = Math.atan2(H, E);
const theta = (a2 - a1) * 0.5;
const phi = (a2 + a1) * 0.5;
return {
translation: this.constructor.translation(this.e, this.f),
rotation: this.constructor.rotation(phi),
scale: this.constructor.nonUniformScaling(scaleX, scaleY),
rotation0: this.constructor.rotation(theta)
};
}
/**
* equals
*
* @param {module:kld-affine.Matrix2D} that
* @returns {boolean}
*/
equals(that) {
return (
this.a === that.a &&
this.b === that.b &&
this.c === that.c &&
this.d === that.d &&
this.e === that.e &&
this.f === that.f
);
}
/**
* precisionEquals
*
* @param {module:kld-affine.Matrix2D} that
* @param {number} precision
* @returns {boolean}
*/
precisionEquals(that, precision) {
return (
Math.abs(this.a - that.a) < precision &&
Math.abs(this.b - that.b) < precision &&
Math.abs(this.c - that.c) < precision &&
Math.abs(this.d - that.d) < precision &&
Math.abs(this.e - that.e) < precision &&
Math.abs(this.f - that.f) < precision
);
}
/**
* toString
*
* @returns {string}
*/
toString() {
return `matrix(${this.a},${this.b},${this.c},${this.d},${this.e},${this.f})`;
}
}
/**
* Identity matrix
*
* @returns {module:kld-affine.Matrix2D}
*/
Matrix2D.IDENTITY = new Matrix2D();
Matrix2D.IDENTITY.isIdentity = () => true;
export default Matrix2D;