Factor camera logic into a separate object in preparation for the 3D demo

This commit is contained in:
Patrick Walton 2017-09-02 12:14:10 -07:00
parent 444e7bbf96
commit 7360a41a60
6 changed files with 125 additions and 90 deletions

View File

@ -19,6 +19,7 @@ import {BUILTIN_FONT_URI, TextLayout, PathfinderGlyph} from "./text";
import {PathfinderError, panic, unwrapNull} from "./utils"; import {PathfinderError, panic, unwrapNull} from "./utils";
import {PathfinderDemoView, Timings} from "./view"; import {PathfinderDemoView, Timings} from "./view";
import SSAAStrategy from "./ssaa-strategy"; import SSAAStrategy from "./ssaa-strategy";
import { OrthographicCamera } from "./camera";
const TEXT: string = "Lorem ipsum dolor sit amet"; const TEXT: string = "Lorem ipsum dolor sit amet";
@ -80,7 +81,10 @@ class ThreeDView extends PathfinderDemoView {
super(commonShaderSource, shaderSources); super(commonShaderSource, shaderSources);
this.appController = appController; 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) { uploadPathMetadata(pathCount: number) {
@ -98,7 +102,6 @@ class ThreeDView extends PathfinderDemoView {
const textGlyph = textGlyphs[pathIndex]; const textGlyph = textGlyphs[pathIndex];
const glyphRect = textGlyph.getRect(PIXELS_PER_UNIT); const glyphRect = textGlyph.getRect(PIXELS_PER_UNIT);
console.log(glyphRect);
pathTransforms.set([1, 1, glyphRect[0], glyphRect[1]], startOffset); 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); 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() { protected get worldTransform() {
const transform = glmatrix.mat4.create(); const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [this.translation[0], this.translation[1], 0]); const translation = this.camera.translation;
glmatrix.mat4.scale(transform, transform, [this.scale, this.scale, 1.0]); glmatrix.mat4.fromTranslation(transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
return transform; return transform;
} }
private _scale: number; private _scale: number;
private appController: ThreeDController; private appController: ThreeDController;
camera: OrthographicCamera;
} }
class ThreeDGlyph extends PathfinderGlyph { class ThreeDGlyph extends PathfinderGlyph {

75
demo/client/src/camera.ts Normal file
View File

@ -0,0 +1,75 @@
// pathfinder/client/src/camera.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;
}

View File

@ -11,13 +11,14 @@
import * as glmatrix from 'gl-matrix'; import * as glmatrix from 'gl-matrix';
import {AppController} from "./app-controller"; import {AppController} from "./app-controller";
import {OrthographicCamera} from "./camera";
import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes"; 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_RIGHT_VERTEX_OFFSET} from "./meshes";
import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, B_QUAD_LOWER_LEFT_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_RIGHT_VERTEX_OFFSET} from "./meshes";
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes"; import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes";
import {BUILTIN_FONT_URI, GlyphStorage, PathfinderGlyph} from "./text"; 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"; import {PathfinderView} from "./view";
const CHARACTER: string = 'r'; const CHARACTER: string = 'r';
@ -67,6 +68,7 @@ class MeshDebuggerView extends PathfinderView {
super(); super();
this.appController = appController; this.appController = appController;
this.camera = new OrthographicCamera(this.canvas);
this.scale = 1.0; this.scale = 1.0;
} }
@ -85,7 +87,8 @@ class MeshDebuggerView extends PathfinderView {
context.clearRect(0, 0, this.canvas.width, this.canvas.height); context.clearRect(0, 0, this.canvas.width, this.canvas.height);
context.save(); 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.scale(this.scale, this.scale);
context.font = POINT_LABEL_FONT; context.font = POINT_LABEL_FONT;
@ -156,6 +159,8 @@ class MeshDebuggerView extends PathfinderView {
protected scale: number; protected scale: number;
private appController: MeshDebuggerAppController; private appController: MeshDebuggerAppController;
camera: OrthographicCamera;
} }
class MeshDebuggerGlyph extends PathfinderGlyph {} class MeshDebuggerGlyph extends PathfinderGlyph {}

View File

@ -14,6 +14,7 @@ import 'path-data-polyfill.js';
import {DemoAppController} from './app-controller'; import {DemoAppController} from './app-controller';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {OrthographicCamera} from "./camera";
import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy"; import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy";
import {PathfinderMeshData} from "./meshes"; import {PathfinderMeshData} from "./meshes";
import {ShaderMap, ShaderProgramSource} from './shader-loader'; import {ShaderMap, ShaderProgramSource} from './shader-loader';
@ -172,7 +173,10 @@ class SVGDemoView extends PathfinderDemoView {
super(commonShaderSource, shaderSources); super(commonShaderSource, shaderSources);
this.appController = appController; 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 { get destAllocatedSize(): glmatrix.vec2 {
@ -223,25 +227,17 @@ class SVGDemoView extends PathfinderDemoView {
return glmatrix.vec2.fromValues(1.0, 1.0); 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() { protected get worldTransform() {
const transform = glmatrix.mat4.create(); const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [this.translation[0], this.translation[1], 0]); const translation = this.camera.translation;
glmatrix.mat4.scale(transform, transform, [this.scale, this.scale, 1.0]); glmatrix.mat4.fromTranslation(transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
return transform; return transform;
} }
private _scale: number;
private appController: SVGDemoController; private appController: SVGDemoController;
camera: OrthographicCamera;
} }
function main() { function main() {

View File

@ -27,6 +27,7 @@ import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic}
import {MonochromePathfinderView, Timings} from './view'; import {MonochromePathfinderView, Timings} from './view';
import PathfinderBufferTexture from './buffer-texture'; import PathfinderBufferTexture from './buffer-texture';
import SSAAStrategy from './ssaa-strategy'; import SSAAStrategy from './ssaa-strategy';
import { OrthographicCamera } from "./camera";
const DEFAULT_TEXT: string = const DEFAULT_TEXT: string =
`Twas brillig, and the slithy toves `Twas brillig, and the slithy toves
@ -227,6 +228,10 @@ class TextDemoView extends MonochromePathfinderView {
this.appController = appController; 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); this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
} }
@ -279,10 +284,11 @@ class TextDemoView extends MonochromePathfinderView {
const pixelsPerUnit = this.appController.pixelsPerUnit; const pixelsPerUnit = this.appController.pixelsPerUnit;
// Only build glyphs in view. // Only build glyphs in view.
const canvasRect = glmatrix.vec4.fromValues(-this.translation[0], const translation = this.camera.translation;
-this.translation[1], const canvasRect = glmatrix.vec4.fromValues(-translation[0],
-this.translation[0] + this.canvas.width, -translation[1],
-this.translation[1] + this.canvas.height); -translation[0] + this.canvas.width,
-translation[1] + this.canvas.height);
let atlasGlyphs = let atlasGlyphs =
textGlyphs.filter(glyph => rectsIntersect(glyph.getRect(pixelsPerUnit), canvasRect)) textGlyphs.filter(glyph => rectsIntersect(glyph.getRect(pixelsPerUnit), canvasRect))
@ -387,8 +393,14 @@ class TextDemoView extends MonochromePathfinderView {
this.setDirty(); this.setDirty();
} }
protected panned() { protected onPan() {
super.panned(); this.setDirty();
this.rebuildAtlasIfNecessary();
}
protected onZoom() {
this.appController.fontSize = this.camera.scale * INITIAL_FONT_SIZE;
this.setDirty();
this.rebuildAtlasIfNecessary(); this.rebuildAtlasIfNecessary();
} }
@ -434,7 +446,7 @@ class TextDemoView extends MonochromePathfinderView {
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]); [2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
glmatrix.mat4.translate(transform, glmatrix.mat4.translate(transform,
transform, transform,
[this.translation[0], this.translation[1], 0.0]); [this.camera.translation[0], this.camera.translation[1], 0.0]);
// Blit. // Blit.
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform); this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
@ -468,14 +480,6 @@ class TextDemoView extends MonochromePathfinderView {
return this.appController.atlas.usedSize; 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): protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
AntialiasingStrategy { AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel);
@ -497,6 +501,8 @@ class TextDemoView extends MonochromePathfinderView {
glyphElementsBuffer: WebGLBuffer; glyphElementsBuffer: WebGLBuffer;
appController: TextDemoController; appController: TextDemoController;
camera: OrthographicCamera;
} }
interface AntialiasingStrategyTable { interface AntialiasingStrategyTable {

View File

@ -17,8 +17,7 @@ import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader'
import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader'; import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils'; import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils';
import PathfinderBufferTexture from './buffer-texture'; import PathfinderBufferTexture from './buffer-texture';
import { Camera } from "./camera";
const SCALE_FACTOR: number = 1.0 / 100.0;
const TIME_INTERVAL_DELAY: number = 32; const TIME_INTERVAL_DELAY: number = 32;
@ -51,12 +50,8 @@ export abstract class PathfinderView {
constructor() { constructor() {
this.dirty = false; this.dirty = false;
this.translation = glmatrix.vec2.create();
this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement; 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); window.addEventListener('resize', () => this.resizeToFit(false), false);
this.resizeToFit(true); this.resizeToFit(true);
} }
@ -78,42 +73,6 @@ export abstract class PathfinderView {
this.resized(); 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 { protected resized(): void {
this.setDirty(); this.setDirty();
} }
@ -129,12 +88,9 @@ export abstract class PathfinderView {
this.dirty = false; this.dirty = false;
} }
protected abstract get scale(): number;
protected abstract set scale(newScale: number);
protected canvas: HTMLCanvasElement; protected canvas: HTMLCanvasElement;
protected translation: glmatrix.vec2; protected camera: Camera;
private dirty: boolean; private dirty: boolean;
} }