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 {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 {

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 {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 {}

View File

@ -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() {

View File

@ -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 {

View File

@ -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;
}