From 7360a41a6029f926475bad02d1343c4f274f916c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 2 Sep 2017 12:14:10 -0700 Subject: [PATCH] Factor camera logic into a separate object in preparation for the 3D demo --- demo/client/src/3d-demo.ts | 23 +++++----- demo/client/src/camera.ts | 75 ++++++++++++++++++++++++++++++++ demo/client/src/mesh-debugger.ts | 9 +++- demo/client/src/svg-demo.ts | 24 +++++----- demo/client/src/text-demo.ts | 36 ++++++++------- demo/client/src/view.ts | 48 +------------------- 6 files changed, 125 insertions(+), 90 deletions(-) create mode 100644 demo/client/src/camera.ts diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index ad453604..921bf5b7 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -19,6 +19,7 @@ import {BUILTIN_FONT_URI, TextLayout, PathfinderGlyph} from "./text"; import {PathfinderError, panic, unwrapNull} from "./utils"; import {PathfinderDemoView, Timings} from "./view"; import SSAAStrategy from "./ssaa-strategy"; +import { OrthographicCamera } from "./camera"; const TEXT: string = "Lorem ipsum dolor sit amet"; @@ -80,7 +81,10 @@ class ThreeDView extends PathfinderDemoView { super(commonShaderSource, shaderSources); this.appController = appController; - this._scale = 1.0; + + this.camera = new OrthographicCamera(this.canvas); + this.camera.onPan = () => this.setDirty(); + this.camera.onZoom = () => this.setDirty(); } uploadPathMetadata(pathCount: number) { @@ -98,7 +102,6 @@ class ThreeDView extends PathfinderDemoView { const textGlyph = textGlyphs[pathIndex]; const glyphRect = textGlyph.getRect(PIXELS_PER_UNIT); - console.log(glyphRect); pathTransforms.set([1, 1, glyphRect[0], glyphRect[1]], startOffset); } @@ -135,25 +138,19 @@ class ThreeDView extends PathfinderDemoView { return glmatrix.vec2.fromValues(1.0, 1.0); } - protected get scale(): number { - return this._scale; - } - - protected set scale(newScale: number) { - this._scale = newScale; - this.setDirty(); - } - protected get worldTransform() { const transform = glmatrix.mat4.create(); - glmatrix.mat4.fromTranslation(transform, [this.translation[0], this.translation[1], 0]); - glmatrix.mat4.scale(transform, transform, [this.scale, this.scale, 1.0]); + const translation = this.camera.translation; + glmatrix.mat4.fromTranslation(transform, [translation[0], translation[1], 0]); + glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]); return transform; } private _scale: number; private appController: ThreeDController; + + camera: OrthographicCamera; } class ThreeDGlyph extends PathfinderGlyph { diff --git a/demo/client/src/camera.ts b/demo/client/src/camera.ts new file mode 100644 index 00000000..f82e446d --- /dev/null +++ b/demo/client/src/camera.ts @@ -0,0 +1,75 @@ +// pathfinder/client/src/camera.ts +// +// Copyright © 2017 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +import * as glmatrix from 'gl-matrix'; +import {PathfinderView} from "./view"; + +const SCALE_FACTOR: number = 1.0 / 100.0; + +export abstract class Camera { + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + } + + protected canvas: HTMLCanvasElement; +} + +export class OrthographicCamera extends Camera { + constructor(canvas: HTMLCanvasElement) { + super(canvas); + + this.translation = glmatrix.vec2.create(); + this.scale = 1.0; + + this.canvas.addEventListener('wheel', event => this.onWheel(event), false); + + this.onPan = null; + this.onZoom = null; + } + + onWheel(event: MouseWheelEvent) { + event.preventDefault(); + + if (event.ctrlKey) { + // Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel + const mouseLocation = glmatrix.vec2.fromValues(event.clientX, event.clientY); + const canvasLocation = this.canvas.getBoundingClientRect(); + mouseLocation[0] -= canvasLocation.left; + mouseLocation[1] = canvasLocation.bottom - mouseLocation[1]; + glmatrix.vec2.scale(mouseLocation, mouseLocation, window.devicePixelRatio); + + const absoluteTranslation = glmatrix.vec2.create(); + glmatrix.vec2.sub(absoluteTranslation, this.translation, mouseLocation); + glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, 1.0 / this.scale); + + this.scale *= 1.0 - event.deltaY * window.devicePixelRatio * SCALE_FACTOR; + + glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, this.scale); + glmatrix.vec2.add(this.translation, absoluteTranslation, mouseLocation); + + if (this.onZoom != null) + this.onZoom(); + } else { + // Pan event. + const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY); + glmatrix.vec2.scale(delta, delta, window.devicePixelRatio); + glmatrix.vec2.add(this.translation, this.translation, delta); + + if (this.onPan != null) + this.onPan(); + } + } + + onPan: (() => void) | null; + onZoom: (() => void) | null; + + translation: glmatrix.vec2; + scale: number; +} diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts index c8aec258..a50146a9 100644 --- a/demo/client/src/mesh-debugger.ts +++ b/demo/client/src/mesh-debugger.ts @@ -11,13 +11,14 @@ import * as glmatrix from 'gl-matrix'; import {AppController} from "./app-controller"; +import {OrthographicCamera} from "./camera"; import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes"; import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET} from "./meshes"; import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, B_QUAD_LOWER_LEFT_VERTEX_OFFSET} from "./meshes"; import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes"; import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes"; import {BUILTIN_FONT_URI, GlyphStorage, PathfinderGlyph} from "./text"; -import { unwrapNull, UINT32_SIZE, UINT32_MAX } from "./utils"; +import {unwrapNull, UINT32_SIZE, UINT32_MAX} from "./utils"; import {PathfinderView} from "./view"; const CHARACTER: string = 'r'; @@ -67,6 +68,7 @@ class MeshDebuggerView extends PathfinderView { super(); this.appController = appController; + this.camera = new OrthographicCamera(this.canvas); this.scale = 1.0; } @@ -85,7 +87,8 @@ class MeshDebuggerView extends PathfinderView { context.clearRect(0, 0, this.canvas.width, this.canvas.height); context.save(); - context.translate(this.translation[0], this.canvas.height - this.translation[1]); + context.translate(this.camera.translation[0], + this.canvas.height - this.camera.translation[1]); context.scale(this.scale, this.scale); context.font = POINT_LABEL_FONT; @@ -156,6 +159,8 @@ class MeshDebuggerView extends PathfinderView { protected scale: number; private appController: MeshDebuggerAppController; + + camera: OrthographicCamera; } class MeshDebuggerGlyph extends PathfinderGlyph {} diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts index 3e2a8709..81426417 100644 --- a/demo/client/src/svg-demo.ts +++ b/demo/client/src/svg-demo.ts @@ -14,6 +14,7 @@ import 'path-data-polyfill.js'; import {DemoAppController} from './app-controller'; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; +import {OrthographicCamera} from "./camera"; import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy"; import {PathfinderMeshData} from "./meshes"; import {ShaderMap, ShaderProgramSource} from './shader-loader'; @@ -172,7 +173,10 @@ class SVGDemoView extends PathfinderDemoView { super(commonShaderSource, shaderSources); this.appController = appController; - this._scale = 1.0; + + this.camera = new OrthographicCamera(this.canvas); + this.camera.onPan = () => this.setDirty(); + this.camera.onZoom = () => this.setDirty(); } get destAllocatedSize(): glmatrix.vec2 { @@ -223,25 +227,17 @@ class SVGDemoView extends PathfinderDemoView { return glmatrix.vec2.fromValues(1.0, 1.0); } - protected get scale(): number { - return this._scale; - } - - protected set scale(newScale: number) { - this._scale = newScale; - this.setDirty(); - } - protected get worldTransform() { const transform = glmatrix.mat4.create(); - glmatrix.mat4.fromTranslation(transform, [this.translation[0], this.translation[1], 0]); - glmatrix.mat4.scale(transform, transform, [this.scale, this.scale, 1.0]); + const translation = this.camera.translation; + glmatrix.mat4.fromTranslation(transform, [translation[0], translation[1], 0]); + glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]); return transform; } - private _scale: number; - private appController: SVGDemoController; + + camera: OrthographicCamera; } function main() { diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index dda5fe17..365bb3fa 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -27,6 +27,7 @@ import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} import {MonochromePathfinderView, Timings} from './view'; import PathfinderBufferTexture from './buffer-texture'; import SSAAStrategy from './ssaa-strategy'; +import { OrthographicCamera } from "./camera"; const DEFAULT_TEXT: string = `’Twas brillig, and the slithy toves @@ -227,6 +228,10 @@ class TextDemoView extends MonochromePathfinderView { this.appController = appController; + this.camera = new OrthographicCamera(this.canvas); + this.camera.onPan = () => this.onPan(); + this.camera.onZoom = () => this.onZoom(); + this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false); } @@ -279,10 +284,11 @@ class TextDemoView extends MonochromePathfinderView { const pixelsPerUnit = this.appController.pixelsPerUnit; // Only build glyphs in view. - const canvasRect = glmatrix.vec4.fromValues(-this.translation[0], - -this.translation[1], - -this.translation[0] + this.canvas.width, - -this.translation[1] + this.canvas.height); + const translation = this.camera.translation; + const canvasRect = glmatrix.vec4.fromValues(-translation[0], + -translation[1], + -translation[0] + this.canvas.width, + -translation[1] + this.canvas.height); let atlasGlyphs = textGlyphs.filter(glyph => rectsIntersect(glyph.getRect(pixelsPerUnit), canvasRect)) @@ -387,8 +393,14 @@ class TextDemoView extends MonochromePathfinderView { this.setDirty(); } - protected panned() { - super.panned(); + protected onPan() { + this.setDirty(); + this.rebuildAtlasIfNecessary(); + } + + protected onZoom() { + this.appController.fontSize = this.camera.scale * INITIAL_FONT_SIZE; + this.setDirty(); this.rebuildAtlasIfNecessary(); } @@ -434,7 +446,7 @@ class TextDemoView extends MonochromePathfinderView { [2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]); glmatrix.mat4.translate(transform, transform, - [this.translation[0], this.translation[1], 0.0]); + [this.camera.translation[0], this.camera.translation[1], 0.0]); // Blit. this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform); @@ -468,14 +480,6 @@ class TextDemoView extends MonochromePathfinderView { return this.appController.atlas.usedSize; } - protected get scale(): number { - return this.appController.fontSize; - } - - protected set scale(newScale: number) { - this.appController.fontSize = newScale; - } - protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): AntialiasingStrategy { return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); @@ -497,6 +501,8 @@ class TextDemoView extends MonochromePathfinderView { glyphElementsBuffer: WebGLBuffer; appController: TextDemoController; + + camera: OrthographicCamera; } interface AntialiasingStrategyTable { diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index 37dcb346..96fb90e3 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -17,8 +17,7 @@ import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader' import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader'; import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils'; import PathfinderBufferTexture from './buffer-texture'; - -const SCALE_FACTOR: number = 1.0 / 100.0; +import { Camera } from "./camera"; const TIME_INTERVAL_DELAY: number = 32; @@ -51,12 +50,8 @@ export abstract class PathfinderView { constructor() { this.dirty = false; - this.translation = glmatrix.vec2.create(); - this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement; - this.canvas.addEventListener('wheel', event => this.onWheel(event), false); - window.addEventListener('resize', () => this.resizeToFit(false), false); this.resizeToFit(true); } @@ -78,42 +73,6 @@ export abstract class PathfinderView { this.resized(); } - private onWheel(event: WheelEvent) { - event.preventDefault(); - - if (event.ctrlKey) { - // Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel - const mouseLocation = glmatrix.vec2.fromValues(event.clientX, event.clientY); - const canvasLocation = this.canvas.getBoundingClientRect(); - mouseLocation[0] -= canvasLocation.left; - mouseLocation[1] = canvasLocation.bottom - mouseLocation[1]; - glmatrix.vec2.scale(mouseLocation, mouseLocation, window.devicePixelRatio); - - const absoluteTranslation = glmatrix.vec2.create(); - glmatrix.vec2.sub(absoluteTranslation, this.translation, mouseLocation); - glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, 1.0 / this.scale); - - this.scale *= 1.0 - event.deltaY * window.devicePixelRatio * SCALE_FACTOR; - - glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, this.scale); - glmatrix.vec2.add(this.translation, absoluteTranslation, mouseLocation); - - this.setDirty(); - return; - } - - // Pan event. - const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY); - glmatrix.vec2.scale(delta, delta, window.devicePixelRatio); - glmatrix.vec2.add(this.translation, this.translation, delta); - - this.panned(); - } - - protected panned(): void { - this.setDirty(); - } - protected resized(): void { this.setDirty(); } @@ -129,12 +88,9 @@ export abstract class PathfinderView { this.dirty = false; } - protected abstract get scale(): number; - protected abstract set scale(newScale: number); - protected canvas: HTMLCanvasElement; - protected translation: glmatrix.vec2; + protected camera: Camera; private dirty: boolean; }