From acf2e0be0045cdc890481e34b63a9b5f355ab4f3 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 6 Sep 2017 14:11:58 -0700 Subject: [PATCH] Implement mesh expansion so that the 3D demo can actually render strings of text --- demo/client/src/3d-demo.ts | 11 ++-- demo/client/src/meshes.ts | 15 +++-- demo/client/src/text.ts | 113 ++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 10 deletions(-) diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index 1d67f0e5..d5157076 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -53,11 +53,12 @@ class ThreeDController extends DemoAppController { protected fileLoaded(): void { this.layout = new TextLayout(this.fileData, TEXT, glyph => new ThreeDGlyph(glyph)); this.layout.layoutText(); - this.layout.glyphStorage.partition().then((meshes: PathfinderMeshData) => { - this.meshes = meshes; + this.layout.glyphStorage.partition().then((baseMeshes: PathfinderMeshData) => { + this.baseMeshes = baseMeshes; + this.expandedMeshes = this.layout.glyphStorage.expandMeshes(baseMeshes).meshes; this.view.then(view => { view.uploadPathMetadata(this.layout.glyphStorage.textGlyphs.length); - view.attachMeshes(this.meshes); + view.attachMeshes(this.expandedMeshes); }); }); } @@ -77,7 +78,9 @@ class ThreeDController extends DemoAppController { } layout: TextLayout; - private meshes: PathfinderMeshData; + + private baseMeshes: PathfinderMeshData; + private expandedMeshes: PathfinderMeshData; } class ThreeDView extends PathfinderDemoView { diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index 55d2cc76..f83bf075 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -10,7 +10,7 @@ import * as base64js from 'base64-js'; -import {PathfinderError, expectNotNull} from './utils'; +import {PathfinderError, expectNotNull, panic} from './utils'; const BUFFER_TYPES: Meshes = { bQuads: 'ARRAY_BUFFER', @@ -51,9 +51,16 @@ export interface Meshes { } export class PathfinderMeshData implements Meshes { - constructor(meshes: any) { - for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) - this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer as ArrayBuffer; + constructor(meshes: Meshes) { + for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) { + const meshBuffer = meshes[bufferName]; + if (typeof(meshBuffer) === 'string') + this[bufferName] = base64js.toByteArray(meshBuffer).buffer as ArrayBuffer; + else if (meshBuffer instanceof ArrayBuffer) + this[bufferName] = meshBuffer; + else + panic("Unknown buffer type!"); + } this.bQuadCount = this.bQuads.byteLength / B_QUAD_SIZE; this.edgeUpperLineIndexCount = this.edgeUpperLineIndices.byteLength / 8; diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index 70171fd0..d831ea50 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -14,13 +14,17 @@ import * as glmatrix from 'gl-matrix'; import * as _ from 'lodash'; import * as opentype from "opentype.js"; -import {PathfinderMeshData} from "./meshes"; -import {assert, panic} from "./utils"; +import {B_QUAD_SIZE, PathfinderMeshData} from "./meshes"; +import {UINT32_SIZE, UINT32_MAX, assert, panic} from "./utils"; export const BUILTIN_FONT_URI: string = "/otf/demo"; const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font"; +export interface ExpandedMeshData { + meshes: PathfinderMeshData; +} + type CreateGlyphFn = (glyph: opentype.Glyph) => Glyph; export interface PixelMetrics { @@ -89,6 +93,91 @@ export class GlyphStorage { }); } + expandMeshes(meshes: PathfinderMeshData): ExpandedMeshData { + const bQuads = _.chunk(new Uint32Array(meshes.bQuads), B_QUAD_SIZE / UINT32_SIZE); + const bVertexPositions = new Float32Array(meshes.bVertexPositions); + const bVertexPathIDs = new Uint16Array(meshes.bVertexPathIDs); + const bVertexLoopBlinnData = new Uint32Array(meshes.bVertexLoopBlinnData); + + const expandedBQuads: number[] = []; + const expandedBVertexPositions: number[] = []; + const expandedBVertexPathIDs: number[] = []; + const expandedBVertexLoopBlinnData: number[] = []; + const expandedCoverInteriorIndices: number[] = []; + const expandedCoverCurveIndices: number[] = []; + + for (let textGlyphIndex = 0; textGlyphIndex < this.textGlyphs.length; textGlyphIndex++) { + const textGlyph = this.textGlyphs[textGlyphIndex]; + const uniqueGlyphIndex = _.sortedIndexBy(this.uniqueGlyphs, textGlyph, 'index'); + if (uniqueGlyphIndex < 0) + continue; + const firstBVertexIndex = _.sortedIndex(bVertexPathIDs, uniqueGlyphIndex + 1); + if (firstBVertexIndex < 0) + continue; + + // Copy over vertices. + let bVertexIndex = firstBVertexIndex; + const firstExpandedBVertexIndex = expandedBVertexPathIDs.length; + while (bVertexIndex < bVertexPathIDs.length && + bVertexPathIDs[bVertexIndex] === uniqueGlyphIndex + 1) { + expandedBVertexPositions.push(bVertexPositions[bVertexIndex * 2 + 0], + bVertexPositions[bVertexIndex * 2 + 1]); + expandedBVertexPathIDs.push(textGlyphIndex + 1); + expandedBVertexLoopBlinnData.push(bVertexLoopBlinnData[bVertexIndex]); + bVertexIndex++; + } + + // Copy over indices. + copyIndices(expandedCoverInteriorIndices, + new Uint32Array(meshes.coverInteriorIndices), + firstExpandedBVertexIndex, + firstBVertexIndex, + bVertexIndex); + copyIndices(expandedCoverCurveIndices, + new Uint32Array(meshes.coverCurveIndices), + firstExpandedBVertexIndex, + firstBVertexIndex, + bVertexIndex); + + // Copy over B-quads. + let firstBQuadIndex = + _.findIndex(bQuads, bQuad => bVertexPathIDs[bQuad[0]] == uniqueGlyphIndex + 1); + if (firstBQuadIndex < 0) + firstBQuadIndex = bQuads.length; + const indexDelta = firstExpandedBVertexIndex - firstBVertexIndex; + for (let bQuadIndex = firstBQuadIndex; bQuadIndex < bQuads.length; bQuadIndex++) { + const bQuad = bQuads[bQuadIndex]; + if (bVertexPathIDs[bQuad[0]] !== uniqueGlyphIndex + 1) + break; + for (let indexIndex = 0; indexIndex < B_QUAD_SIZE / UINT32_SIZE; indexIndex++) { + const srcIndex = bQuad[indexIndex]; + if (srcIndex === UINT32_MAX) + expandedBQuads.push(srcIndex); + else + expandedBQuads.push(srcIndex + indexDelta); + } + } + } + + return { + meshes: new PathfinderMeshData({ + bQuads: new Uint32Array(expandedBQuads).buffer as ArrayBuffer, + bVertexPositions: new Float32Array(expandedBVertexPositions).buffer as ArrayBuffer, + bVertexPathIDs: new Uint16Array(expandedBVertexPathIDs).buffer as ArrayBuffer, + bVertexLoopBlinnData: new Uint32Array(expandedBVertexLoopBlinnData).buffer as + ArrayBuffer, + coverInteriorIndices: new Uint32Array(expandedCoverInteriorIndices).buffer as + ArrayBuffer, + coverCurveIndices: new Uint32Array(expandedCoverCurveIndices).buffer as + ArrayBuffer, + edgeUpperCurveIndices: new ArrayBuffer(0), + edgeUpperLineIndices: new ArrayBuffer(0), + edgeLowerCurveIndices: new ArrayBuffer(0), + edgeLowerLineIndices: new ArrayBuffer(0), + }) + } + } + readonly fontData: ArrayBuffer; readonly font: Font; readonly textGlyphs: Glyph[]; @@ -199,3 +288,23 @@ export abstract class PathfinderGlyph { /// In font units, relative to (0, 0). origin: glmatrix.vec2; } + +function copyIndices(destIndices: number[], + srcIndices: Uint32Array, + firstExpandedIndex: number, + firstIndex: number, + lastIndex: number) { + // FIXME(pcwalton): Use binary search instead of linear search. + const indexDelta = firstExpandedIndex - firstIndex; + let indexIndex = _.findIndex(srcIndices, + srcIndex => srcIndex >= firstIndex && srcIndex < lastIndex); + if (indexIndex < 0) + return; + while (indexIndex < srcIndices.length) { + const index = srcIndices[indexIndex]; + if (index < firstIndex || index >= lastIndex) + break; + destIndices.push(index + indexDelta); + indexIndex++; + } +}