Janky zooming!
This commit is contained in:
@@ -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<FlamechartViewProps, void> {
|
||||
}
|
||||
|
||||
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<FlamechartViewProps, void> {
|
||||
|
||||
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<FlamechartViewProps, void> {
|
||||
}
|
||||
|
||||
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<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)
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
30
math.ts
30
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()),
|
||||
|
||||
Reference in New Issue
Block a user