From f26a025e7d4ae1d10018548cfe4a25eaea809052 Mon Sep 17 00:00:00 2001 From: Jamie Wong Date: Fri, 24 Nov 2017 00:35:24 -0800 Subject: [PATCH] Janky zooming! --- flamechart.tsx | 67 ++++++++++++++++++++++++++++++++++---------------- math.ts | 30 ++++++++++++++++++++++ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/flamechart.tsx b/flamechart.tsx index 86a347e..cfa113c 100644 --- a/flamechart.tsx +++ b/flamechart.tsx @@ -3,7 +3,7 @@ import {StyleSheet, css} from 'aphrodite' import {Profile, Frame} from './profile' import regl, {vec2, vec3, mat3, ReglCommand, ReglCommandConstructor} from 'regl' -import { Rect, Vec2, AffineTransform } from './math' +import { Rect, Vec2, AffineTransform, clamp } from './math' interface FlamechartFrame { frame: Frame @@ -88,12 +88,10 @@ export class FlamechartView extends Component { } this.canvas = element as HTMLCanvasElement - const viewportWidth = this.canvas.width - const viewportHeight = this.canvas.height this.worldSpaceViewportRect = new Rect( new Vec2(0, 0), - new Vec2(viewportWidth, viewportHeight) + new Vec2(this.viewportWidth(), this.viewportHeight()) ) const ctx = this.canvas.getContext('webgl')! @@ -104,11 +102,13 @@ export class FlamechartView extends Component { private configSpaceWidth() { return this.props.flamechart.getDuration() } private configSpaceHeight() { return this.props.flamechart.getLayers().length } + private configSpaceSize() { return new Vec2(this.configSpaceWidth(), this.configSpaceHeight()) } private WORLD_SPACE_FRAME_HEIGHT = 16 private viewportWidth() { return this.canvas ? this.canvas.width : 0 } private viewportHeight() { return this.canvas ? this.canvas.height : 0 } + private viewportSize() { return new Vec2(this.viewportWidth(), this.viewportHeight()) } private configSpaceToWorldSpace() { return AffineTransform.withScale(new Vec2( @@ -118,14 +118,21 @@ export class FlamechartView extends Component { } private worldSpaceToViewSpace() { - return AffineTransform - .withTranslation(this.worldSpaceViewportRect.origin.times(-1)) + const viewportRect = this.worldSpaceViewportRect + + return AffineTransform.betweenRects( + this.worldSpaceViewportRect, + new Rect(new Vec2(0, 0), this.viewportSize()) + ) } private viewSpaceToNDC() { - return AffineTransform - .withScale(new Vec2(2 / this.viewportWidth(), -2 / this.viewportHeight())) - .withTranslation(new Vec2(-1, 1)) + return AffineTransform.withScale(new Vec2(1, -1)).times( + AffineTransform.betweenRects( + new Rect(new Vec2(0, 0), this.viewportSize()), + new Rect(new Vec2(-1, -1), new Vec2(2, 2)) + ) + ) } private configSpaceToNDC() { @@ -142,22 +149,40 @@ export class FlamechartView extends Component { } } + 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) + 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)) + } + + private zoom(deltaY: number) { + const viewportRect = this.worldSpaceViewportRect + + const multiplier = 1 + 0.001 * deltaY + const newWidth = viewportRect.size.x * multiplier + + this.worldSpaceViewportRect = this.worldSpaceViewportRect + .withSize(viewportRect.size.withX(clamp(newWidth, 5, this.viewportWidth()))) + } + private onWheel = (ev: WheelEvent) => { ev.preventDefault() - const newOrigin = this.worldSpaceViewportRect.origin.plus(new Vec2(ev.deltaX, ev.deltaY)) - const worldSpacePanningBounds = new Rect( - new Vec2(0, 0), - new Vec2( - this.configSpaceWidth() * this.configSpaceToWorldSpace().getScale().x - this.viewportWidth(), - this.configSpaceHeight() * this.configSpaceToWorldSpace().getScale().y - this.viewportHeight() - ) - ) + if (ev.metaKey) { + this.zoom(ev.deltaY) + } else { + this.pan(ev.deltaX, ev.deltaY) + } - this.worldSpaceViewportRect = new Rect( - worldSpacePanningBounds.closestPointTo(newOrigin), - this.worldSpaceViewportRect.size - ) this.renderGL() } diff --git a/math.ts b/math.ts index edb8003..677e5de 100644 --- a/math.ts +++ b/math.ts @@ -6,9 +6,13 @@ export function clamp(x: number, minVal: number, maxVal: number) { 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()) } @@ -58,6 +62,15 @@ export class AffineTransform { } getTranslation() { return new Vec2(this.m02, this.m12) } + static betweenRects(from: Rect, to: Rect) { + return AffineTransform + .withTranslation(to.origin.minus(from.origin)) + .withScale(new Vec2( + to.size.x / from.size.x, + to.size.y / from.size.y + )) + } + times(other: AffineTransform) { const m00 = this.m00 * other.m00 + this.m01 * other.m10 const m01 = this.m00 * other.m01 + this.m01 * other.m11 @@ -69,6 +82,20 @@ export class AffineTransform { return new AffineTransform(m00, m01, m02, m10, m11, m12) } + transformVector(v: Vec2) { + return new Vec2( + v.x * this.m00 + v.y * this.m01, + v.x * this.m10 + v.y * this.m11 + ) + } + + 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 + ) + } + flatten(): [number, number, number, number, number, number, number, number, number] { // Flatten into GLSL format return [ @@ -99,6 +126,9 @@ export class Rect { 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()),