Start the camera centered on the text in the text demo

This commit is contained in:
Patrick Walton 2017-09-11 16:07:11 -07:00
parent 6e4f0b0734
commit 3dd1d73f81
7 changed files with 85 additions and 19 deletions

View File

@ -156,7 +156,7 @@ class ThreeDController extends DemoAppController<ThreeDView> {
}
}
textFrames.push(new TextFrame(textRuns));
textFrames.push(new TextFrame(textRuns, font));
}
this.glyphStorage = new GlyphStorage(this.fileData, textFrames, createGlyph, font);

View File

@ -35,7 +35,7 @@ class BenchmarkAppController extends AppController {
const createGlyph = (glyph: opentype.Glyph) => new BenchmarkGlyph(glyph);
const textRun = new TextRun<BenchmarkGlyph>(STRING, [0, 0], font, createGlyph);
const textFrame = new TextFrame([textRun]);
const textFrame = new TextFrame([textRun], font);
this.glyphStorage = new GlyphStorage(this.fileData, [textFrame], createGlyph, font);
this.glyphStorage.partition().then(meshes => {

View File

@ -15,8 +15,8 @@ import {PathfinderView} from "./view";
const ORTHOGRAPHIC_ZOOM_SPEED: number = 1.0 / 100.0;
const ZOOM_IN_FACTOR: number = 1.2;
const ZOOM_OUT_FACTOR: number = 1.0 / ZOOM_IN_FACTOR;
const ORTHOGRAPHIC_ZOOM_IN_FACTOR: number = 1.2;
const ORTHOGRAPHIC_ZOOM_OUT_FACTOR: number = 1.0 / ORTHOGRAPHIC_ZOOM_IN_FACTOR;
const PERSPECTIVE_MOVEMENT_SPEED: number = 10.0;
const PERSPECTIVE_ROTATION_SPEED: number = 1.0 / 300.0;
@ -28,9 +28,9 @@ const PERSPECTIVE_MOVEMENT_VECTORS: PerspectiveMovementVectors = _.fromPairs([
['D'.charCodeAt(0), glmatrix.vec3.fromValues(-PERSPECTIVE_MOVEMENT_SPEED, 0, 0)],
]);
const MOVEMENT_INTERVAL_DELAY: number = 10;
const PERSPECTIVE_MOVEMENT_INTERVAL_DELAY: number = 10;
const INITIAL_TRANSLATION: glmatrix.vec3 = glmatrix.vec3.fromValues(0.0, 0.0, -1000.0);
const PERSPECTIVE_INITIAL_TRANSLATION: glmatrix.vec3 = glmatrix.vec3.fromValues(0.0, 0.0, -1000.0);
interface PerspectiveMovementVectors {
[keyCode: number]: glmatrix.vec3;
@ -54,6 +54,8 @@ export class OrthographicCamera extends Camera {
this.translation = glmatrix.vec2.create();
this.scale = 1.0;
this._bounds = glmatrix.vec4.create();
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
@ -104,12 +106,29 @@ export class OrthographicCamera extends Camera {
this.onPan();
}
private zoomToFit(): void {
const upperLeft = glmatrix.vec2.fromValues(this._bounds[0], this._bounds[1]);
const lowerRight = glmatrix.vec2.fromValues(this._bounds[2], this._bounds[3]);
// Center.
this.translation = glmatrix.vec2.create();
glmatrix.vec2.lerp(this.translation, upperLeft, lowerRight, 0.5);
glmatrix.vec2.scale(this.translation, this.translation, -this.scale);
this.translation[0] += this.canvas.width * 0.5;
this.translation[1] += this.canvas.height * 0.5;
// TODO(pcwalton): Scale appropriately.
if (this.onPan != null)
this.onPan();
}
zoomIn(): void {
this.zoom(ZOOM_IN_FACTOR, this.centerPoint);
this.zoom(ORTHOGRAPHIC_ZOOM_IN_FACTOR, this.centerPoint);
}
zoomOut(): void {
this.zoom(ZOOM_OUT_FACTOR, this.centerPoint);
this.zoom(ORTHOGRAPHIC_ZOOM_OUT_FACTOR, this.centerPoint);
}
private zoom(scale: number, point: glmatrix.vec2): void {
@ -130,9 +149,20 @@ export class OrthographicCamera extends Camera {
return glmatrix.vec2.fromValues(this.canvas.width * 0.5, this.canvas.height * 0.5);
}
get bounds(): glmatrix.vec4 {
return this._bounds;
}
set bounds(newBounds: glmatrix.vec4) {
this._bounds = glmatrix.vec4.clone(newBounds);
this.zoomToFit();
}
onPan: (() => void) | null;
onZoom: (() => void) | null;
private _bounds: glmatrix.vec4;
translation: glmatrix.vec2;
scale: number;
}
@ -141,7 +171,7 @@ export class PerspectiveCamera extends Camera {
constructor(canvas: HTMLCanvasElement) {
super(canvas);
this.translation = glmatrix.vec3.clone(INITIAL_TRANSLATION);
this.translation = glmatrix.vec3.clone(PERSPECTIVE_INITIAL_TRANSLATION);
this.rotation = glmatrix.vec2.create();
this.movementDelta = glmatrix.vec3.create();
this.movementInterval = null;
@ -198,7 +228,7 @@ export class PerspectiveCamera extends Camera {
private startMoving(): void {
if (this.movementInterval == null)
this.movementInterval = window.setInterval(() => this.move(), MOVEMENT_INTERVAL_DELAY);
this.movementInterval = window.setInterval(() => this.move(), PERSPECTIVE_MOVEMENT_INTERVAL_DELAY);
}
private stopMoving(): void {

View File

@ -45,7 +45,7 @@ class MeshDebuggerAppController extends AppController {
const createGlyph = (glyph: opentype.Glyph) => new MeshDebuggerGlyph(glyph);
const textRun = new TextRun<MeshDebuggerGlyph>(CHARACTER, [0, 0], font, createGlyph);
const textFrame = new TextFrame([textRun]);
const textFrame = new TextFrame([textRun], font);
this.glyphStorage = new GlyphStorage(this.fileData, [textFrame], createGlyph, font);
this.glyphStorage.partition().then(meshes => {

View File

@ -24,7 +24,7 @@ import {UniformMap} from './gl-utils';
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
import {BUILTIN_FONT_URI, Hint, PathfinderGlyph, SimpleTextLayout} from "./text";
import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils';
import { PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic, scaleRect } from './utils';
import {MonochromePathfinderView, Timings} from './view';
import PathfinderBufferTexture from './buffer-texture';
import SSAAStrategy from './ssaa-strategy';
@ -277,9 +277,13 @@ class TextDemoView extends MonochromePathfinderView {
/// Lays out glyphs on the canvas.
private layoutGlyphs() {
this.appController.layout.layoutRuns();
const layout = this.appController.layout;
layout.layoutRuns();
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
const textBounds = scaleRect(layout.textFrame.bounds, this.appController.pixelsPerUnit);
this.camera.bounds = textBounds;
const textGlyphs = layout.glyphStorage.allGlyphs;
const glyphPositions = new Float32Array(textGlyphs.length * 8);
const glyphIndices = new Uint32Array(textGlyphs.length * 6);

View File

@ -63,13 +63,19 @@ export class TextRun<Glyph extends PathfinderGlyph> {
}
}
get measure(): number {
const lastGlyph = _.last(this.glyphs);
return lastGlyph == null ? 0.0 : lastGlyph.origin[0] + lastGlyph.advanceWidth;
}
readonly glyphs: Glyph[];
readonly origin: number[];
}
export class TextFrame<Glyph extends PathfinderGlyph> {
constructor(runs: TextRun<Glyph>[]) {
constructor(runs: TextRun<Glyph>[], font: Font) {
this.runs = runs;
this.font = font;
}
expandMeshes(uniqueGlyphs: Glyph[], meshes: PathfinderMeshData): ExpandedMeshData {
@ -161,6 +167,21 @@ export class TextFrame<Glyph extends PathfinderGlyph> {
}
}
get bounds(): glmatrix.vec4 {
if (this.runs.length === 0)
return glmatrix.vec4.create();
const upperLeft = glmatrix.vec2.clone(this.runs[0].origin);
const lowerRight = glmatrix.vec2.clone(_.last(this.runs)!.origin);
const lineHeight = this.font.lineHeight();
upperLeft[1] -= lineHeight;
lowerRight[1] += lineHeight;
lowerRight[0] = _.defaultTo<number>(_.max(this.runs.map(run => run.measure)), 0.0);
return glmatrix.vec4.fromValues(upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]);
}
get allGlyphs(): Glyph[] {
return _.flatMap(this.runs, run => run.glyphs);
@ -168,6 +189,9 @@ export class TextFrame<Glyph extends PathfinderGlyph> {
readonly runs: TextRun<Glyph>[];
readonly origin: glmatrix.vec3;
private readonly font: Font;
}
export class GlyphStorage<Glyph extends PathfinderGlyph> {
@ -246,13 +270,11 @@ export class SimpleTextLayout<Glyph extends PathfinderGlyph> {
const font = opentype.parse(fontData);
assert(font.isSupported(), "The font type is unsupported!");
const os2Table = font.tables.os2;
const lineHeight = os2Table.sTypoAscender - os2Table.sTypoDescender +
os2Table.sTypoLineGap;
const lineHeight = font.lineHeight();
const textRuns: TextRun<Glyph>[] = text.split("\n").map((line, lineNumber) => {
return new TextRun<Glyph>(line, [0.0, -lineHeight * lineNumber], font, createGlyph);
});
this.textFrame = new TextFrame(textRuns);
this.textFrame = new TextFrame(textRuns, font);
this.glyphStorage = new GlyphStorage(fontData, [this.textFrame], createGlyph, font);
}

View File

@ -8,6 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
import * as glmatrix from 'gl-matrix';
export const UINT32_MAX: number = 0xffffffff;
export const UINT32_SIZE: number = 4;
@ -40,6 +42,14 @@ export function unwrapUndef<T>(value: T | undefined): T {
return expectNotUndef(value, "Unexpected `undefined`!");
}
export function scaleRect(rect: glmatrix.vec4, scale: number): glmatrix.vec4 {
const upperLeft = glmatrix.vec2.clone([rect[0], rect[1]]);
const lowerRight = glmatrix.vec2.clone([rect[2], rect[3]]);
glmatrix.vec2.scale(upperLeft, upperLeft, scale);
glmatrix.vec2.scale(lowerRight, lowerRight, scale);
return glmatrix.vec4.clone([upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]]);
}
export class PathfinderError extends Error {
constructor(message?: string | undefined) {
super(message);