speedscope/math.ts
2017-11-25 01:50:48 -08:00

208 lines
5.6 KiB
TypeScript

export function clamp(x: number, minVal: number, maxVal: number) {
if (x < minVal) return minVal
if (x > maxVal) return maxVal
return x
}
export class Vec2 {
constructor(readonly x = 0, readonly y = 0) {}
withX(x: number) { return new Vec2(x, this.y) }
withY(y: number) { return new Vec2(this.x, y) }
plus(other: Vec2) { return new Vec2(this.x + other.x, this.y + other.y) }
minus(other: Vec2) { return new Vec2(this.x - other.x, this.y - other.y) }
times(scalar: number) { return new Vec2(this.x * scalar, this.y * scalar) }
timesPointwise(other: Vec2) { return new Vec2(this.x * other.x, this.y * other.y) }
dot(other: Vec2) { return this.x * other.x + this.y * other.y }
length2() { return this.dot(this) }
length() { return Math.sqrt(this.length2()) }
static min(a: Vec2, b: Vec2) {
return new Vec2(Math.min(a.x, b.x), Math.min(a.y, b.y))
}
static max(a: Vec2, b: Vec2) {
return new Vec2(Math.max(a.x, b.x), Math.max(a.y, b.y))
}
flatten(): [number, number] { return [this.x, this.y] }
}
export class AffineTransform {
constructor(
readonly m00 = 1, readonly m01 = 0, readonly m02 = 0,
readonly m10 = 0, readonly m11 = 1, readonly m12 = 0
) {}
withScale(s: Vec2) {
let {
m00, m01, m02,
m10, m11, m12
} = this
m00 = s.x
m11 = s.y
return new AffineTransform(m00, m01, m02, m10, m11, m12)
}
static withScale(s: Vec2) {
return (new AffineTransform).withScale(s)
}
scaledBy(s: Vec2) { return AffineTransform.withScale(s).times(this) }
getScale() { return new Vec2(this.m00, this.m11) }
withTranslation(t: Vec2) {
let {
m00, m01, m02,
m10, m11, m12
} = this
m02 = t.x
m12 = t.y
return new AffineTransform(m00, m01, m02, m10, m11, m12)
}
static withTranslation(t: Vec2) {
return (new AffineTransform).withTranslation(t)
}
getTranslation() { return new Vec2(this.m02, this.m12) }
translatedBy(t: Vec2) { return AffineTransform.withTranslation(t).times(this) }
static betweenRects(from: Rect, to: Rect) {
return AffineTransform
.withTranslation(from.origin.times(-1))
.scaledBy(new Vec2(
to.size.x / from.size.x,
to.size.y / from.size.y
))
.translatedBy(to.origin)
}
times(other: AffineTransform) {
const m00 = this.m00 * other.m00 + this.m01 * other.m10
const m01 = this.m00 * other.m01 + this.m01 * other.m11
const m02 = this.m00 * other.m02 + this.m01 * other.m12 + this.m02
const m10 = this.m10 * other.m00 + this.m11 * other.m10
const m11 = this.m10 * other.m01 + this.m11 * other.m11
const m12 = this.m10 * other.m02 + this.m11 * other.m12 + this.m12
return new AffineTransform(m00, m01, m02, m10, m11, m12)
}
timesScalar(s: number) {
const {m00, m01, m02, m10, m11, m12} = this
return new AffineTransform(s*m00, s*m01, s*m02, s*m10, s*m11, s*m12)
}
det() {
const {m00, m01, m02, m10, m11, m12} = this
const m20 = 0
const m21 = 0
const m22 = 1
return (
m00 * (m11 * m22 - m12 * m21) -
m01 * (m10 * m22 - m12 * m20) +
m02 * (m10 * m21 - m11 * m20)
)
}
adj() {
const {m00, m01, m02, m10, m11, m12} = this
const m20 = 0
const m21 = 0
const m22 = 1
// Adjugate matrix (a) is the transpose of the
// cofactor matrix (c).
//
// 00 01 02
// 10 11 12
// 20 21 22
const a00 = /* c00 = */ +(m11 * m22 - m12 * m21)
const a01 = /* c10 = */ -(m01 * m22 - m02 * m21)
const a02 = /* c20 = */ +(m01 * m12 - m02 * m11)
const a10 = /* c01 = */ -(m10 * m22 - m12 * m20)
const a11 = /* c11 = */ +(m00 * m22 - m02 * m20)
const a12 = /* c21 = */ -(m00 * m12 - m02 * m10)
return new AffineTransform(a00, a01, a02, a10, a11, a12)
}
inverted(): AffineTransform | null {
const det = this.det()
if (det === 0) return null
const adj = this.adj()
return adj.timesScalar(1 / det)
}
transformVector(v: Vec2) {
return new Vec2(
v.x * this.m00 + v.y * this.m01,
v.x * this.m10 + v.y * this.m11
)
}
inverseTransformVector(v: Vec2): Vec2 | null {
const inv = this.inverted()
if (!inv) return null
return inv.transformVector(v)
}
transformPosition(v: Vec2) {
return new Vec2(
v.x * this.m00 + v.y * this.m01 + this.m02,
v.x * this.m10 + v.y * this.m11 + this.m12
)
}
inverseTransformPosition(v: Vec2): Vec2 | null {
const inv = this.inverted()
if (!inv) return null
return inv.transformPosition(v)
}
transformRect(r: Rect) {
return new Rect(
this.transformPosition(r.origin),
this.transformVector(r.size)
)
}
flatten(): [number, number, number, number, number, number, number, number, number] {
// Flatten into GLSL format
return [
this.m00, this.m10, 0,
this.m01, this.m11, 0,
this.m02, this.m12, 1,
]
}
}
export class Rect {
constructor(
readonly origin = new Vec2(),
readonly size = new Vec2()
) {}
width() { return this.size.x }
height() { return this.size.y }
left() { return this.origin.x }
right() { return this.left() + this.width() }
top() { return this.origin.y }
bottom() { return this.top() + this.height() }
topLeft() { return this.origin }
topRight() { return this.origin.plus(new Vec2(this.width(), 0)) }
bottomRight() { return this.origin.plus(this.size) }
bottomLeft() { return this.origin.plus(new Vec2(0, this.height())) }
withOrigin(origin: Vec2) { return new Rect(origin, this.size) }
withSize(size: Vec2) { return new Rect(this.origin, size) }
closestPointTo(p: Vec2) {
return new Vec2(
clamp(p.x, this.left(), this.right()),
clamp(p.y, this.top(), this.bottom())
)
}
}