Zoom around cursor

This commit is contained in:
Jamie Wong
2017-11-25 01:50:48 -08:00
parent f26a025e7d
commit 8fa4e0c672
2 changed files with 102 additions and 20 deletions

View File

@@ -118,8 +118,6 @@ export class FlamechartView extends Component<FlamechartViewProps, void> {
}
private worldSpaceToViewSpace() {
const viewportRect = this.worldSpaceViewportRect
return AffineTransform.betweenRects(
this.worldSpaceViewportRect,
new Rect(new Vec2(0, 0), this.viewportSize())
@@ -149,38 +147,52 @@ export class FlamechartView extends Component<FlamechartViewProps, void> {
}
}
private pan(deltaX: number, deltaY: number) {
const worldSpaceDelta = new Vec2(
deltaX / this.worldSpaceToViewSpace().getScale().x,
deltaY / this.worldSpaceToViewSpace().getScale().y
)
const newOrigin = this.worldSpaceViewportRect.origin.plus(worldSpaceDelta)
private pan(viewSpaceDelta: Vec2) {
const worldSpaceDelta = this.worldSpaceToViewSpace().inverseTransformVector(viewSpaceDelta)
if (!worldSpaceDelta) return
// TODO(jlfwong): Add back clamping of panning bounds
/*
const worldSpacePanningBounds = new Rect(
new Vec2(0, 0),
this.configSpaceToWorldSpace().transformVector(this.configSpaceSize()).minus(this.worldSpaceViewportRect.size)
)
*/
this.worldSpaceViewportRect = this.worldSpaceViewportRect
.withOrigin(worldSpacePanningBounds.closestPointTo(newOrigin))
this.worldSpaceViewportRect = AffineTransform.withTranslation(worldSpaceDelta)
.transformRect(this.worldSpaceViewportRect)
}
private zoom(deltaY: number) {
const viewportRect = this.worldSpaceViewportRect
private zoom(worldSpaceZoomCenter: Vec2, multiplier: number) {
/*
multiplier = clamp(multiplier,
5 / this.worldSpaceViewportRect.width(),
this.viewportWidth() / this.worldSpaceViewportRect.width()
)
*/
const multiplier = 1 + 0.001 * deltaY
const newWidth = viewportRect.size.x * multiplier
const zoomTransform = AffineTransform
.withTranslation(worldSpaceZoomCenter.times(-1))
.scaledBy(new Vec2(multiplier, 1))
.translatedBy(worldSpaceZoomCenter)
this.worldSpaceViewportRect = this.worldSpaceViewportRect
.withSize(viewportRect.size.withX(clamp(newWidth, 5, this.viewportWidth())))
this.worldSpaceViewportRect = zoomTransform.transformRect(this.worldSpaceViewportRect)
}
private onWheel = (ev: WheelEvent) => {
ev.preventDefault()
if (ev.metaKey) {
this.zoom(ev.deltaY)
const viewSpaceZoomCenter = new Vec2(ev.offsetX, ev.offsetY)
const worldSpaceZoomCenter = this.worldSpaceToViewSpace().inverseTransformPosition(viewSpaceZoomCenter)
if (worldSpaceZoomCenter) {
const multiplier = 1 + (ev.deltaY / 100)
this.zoom(worldSpaceZoomCenter, multiplier)
}
} else {
this.pan(ev.deltaX, ev.deltaY)
this.pan(new Vec2(ev.deltaX, ev.deltaY))
}
this.renderGL()

74
math.ts
View File

@@ -46,6 +46,7 @@ export class AffineTransform {
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) {
@@ -61,14 +62,16 @@ export class AffineTransform {
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(to.origin.minus(from.origin))
.withScale(new Vec2(
.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) {
@@ -82,6 +85,54 @@ export class AffineTransform {
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,
@@ -89,6 +140,12 @@ export class AffineTransform {
)
}
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,
@@ -96,6 +153,19 @@ export class AffineTransform {
)
}
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 [