2017-08-29 22:46:18 -04:00
|
|
|
// pathfinder/client/src/3d-demo.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.
|
2017-08-31 19:11:09 -04:00
|
|
|
|
2017-08-31 20:08:22 -04:00
|
|
|
import * as glmatrix from 'gl-matrix';
|
2017-09-28 17:34:48 -04:00
|
|
|
import * as _ from "lodash";
|
2017-09-07 01:11:32 -04:00
|
|
|
import * as opentype from "opentype.js";
|
2017-08-31 20:08:22 -04:00
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
import {mat4, vec2} from "gl-matrix";
|
2017-08-31 20:08:22 -04:00
|
|
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
2017-09-30 01:12:09 -04:00
|
|
|
import {SubpixelAAType} from "./aa-strategy";
|
2017-09-01 21:11:44 -04:00
|
|
|
import {DemoAppController} from "./app-controller";
|
2017-10-17 18:30:33 -04:00
|
|
|
import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey} from './atlas';
|
2017-09-28 17:34:48 -04:00
|
|
|
import PathfinderBufferTexture from "./buffer-texture";
|
2017-10-17 18:30:33 -04:00
|
|
|
import {CameraView, PerspectiveCamera} from "./camera";
|
2017-10-06 19:11:53 -04:00
|
|
|
import {UniformMap} from './gl-utils';
|
2018-03-07 17:06:54 -05:00
|
|
|
import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
|
2017-12-05 22:08:31 -05:00
|
|
|
import {PathTransformBuffers, Renderer} from './renderer';
|
2017-08-31 19:11:09 -04:00
|
|
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
2017-09-28 17:34:48 -04:00
|
|
|
import SSAAStrategy from "./ssaa-strategy";
|
2017-09-27 16:02:32 -04:00
|
|
|
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
|
2017-10-17 18:30:33 -04:00
|
|
|
import {calculatePixelRectForGlyph, GlyphStore, Hint, PathfinderFont} from "./text";
|
|
|
|
import {SimpleTextLayout, TextFrame, TextRun, UnitMetrics} from "./text";
|
|
|
|
import {TextRenderContext, TextRenderer} from './text-renderer';
|
2017-11-03 21:49:25 -04:00
|
|
|
import {assert, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from "./utils";
|
|
|
|
import {unwrapNull} from "./utils";
|
2017-10-03 18:24:56 -04:00
|
|
|
import {DemoView, Timings} from "./view";
|
2017-08-31 20:08:22 -04:00
|
|
|
|
2017-09-12 22:43:43 -04:00
|
|
|
const TEXT_AVAILABLE_WIDTH: number = 150000;
|
2017-09-14 18:16:06 -04:00
|
|
|
const TEXT_PADDING: number = 2000;
|
|
|
|
|
|
|
|
const TEXT_SCALE: glmatrix.vec3 = glmatrix.vec3.fromValues(1.0 / 200.0, 1.0 / 200.0, 1.0 / 200.0);
|
2017-09-07 01:11:32 -04:00
|
|
|
|
2017-09-07 01:50:07 -04:00
|
|
|
const TEXT_DATA_URI: string = "/data/mozmonument.json";
|
2017-08-31 20:08:22 -04:00
|
|
|
|
|
|
|
const FONT: string = 'open-sans';
|
|
|
|
|
2017-08-31 22:19:26 -04:00
|
|
|
const PIXELS_PER_UNIT: number = 1.0;
|
|
|
|
|
2017-09-02 16:41:08 -04:00
|
|
|
const FOV: number = 45.0;
|
2018-03-09 18:19:06 -05:00
|
|
|
export const NEAR_CLIP_PLANE: number = 0.1;
|
|
|
|
export const FAR_CLIP_PLANE: number = 100000.0;
|
2017-09-07 22:01:55 -04:00
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
const ATLAS_FONT_SIZE: number = 48;
|
|
|
|
|
|
|
|
const MAX_DISTANCE: number = 200.0;
|
|
|
|
|
2017-09-12 22:43:43 -04:00
|
|
|
const TEXT_TRANSLATION: number[] = [
|
2017-09-14 18:16:06 -04:00
|
|
|
-TEXT_AVAILABLE_WIDTH * 0.5,
|
2017-09-12 22:43:43 -04:00
|
|
|
0.0,
|
2017-09-14 18:16:06 -04:00
|
|
|
TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING,
|
2017-09-12 22:43:43 -04:00
|
|
|
];
|
2017-09-09 03:04:35 -04:00
|
|
|
|
|
|
|
const MONUMENT_TRANSLATION: glmatrix.vec3 = glmatrix.vec3.fromValues(0.0, -690.0, 0.0);
|
|
|
|
const MONUMENT_SCALE: glmatrix.vec3 =
|
2017-09-14 18:16:06 -04:00
|
|
|
glmatrix.vec3.fromValues((TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING) * TEXT_SCALE[0],
|
2017-09-09 03:04:35 -04:00
|
|
|
700.0,
|
2017-09-14 18:16:06 -04:00
|
|
|
(TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING) * TEXT_SCALE[2]);
|
2017-09-09 03:04:35 -04:00
|
|
|
|
|
|
|
const TEXT_COLOR: Uint8Array = new Uint8Array([0xf2, 0xf8, 0xf8, 0xff]);
|
2017-11-03 21:49:25 -04:00
|
|
|
|
|
|
|
const AMBIENT_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.063, 0.063, 0.063]);
|
|
|
|
const DIFFUSE_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.356, 0.264, 0.136]);
|
|
|
|
const SPECULAR_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.490, 0.420, 0.324]);
|
|
|
|
|
|
|
|
const MONUMENT_SHININESS: number = 32.0;
|
2017-09-09 03:04:35 -04:00
|
|
|
|
|
|
|
const CUBE_VERTEX_POSITIONS: Float32Array = new Float32Array([
|
|
|
|
-1.0, -1.0, -1.0, // 0
|
|
|
|
1.0, -1.0, -1.0, // 1
|
|
|
|
-1.0, -1.0, 1.0, // 2
|
|
|
|
1.0, -1.0, 1.0, // 3
|
|
|
|
-1.0, 1.0, -1.0, // 4
|
|
|
|
1.0, 1.0, -1.0, // 5
|
|
|
|
-1.0, 1.0, 1.0, // 6
|
|
|
|
1.0, 1.0, 1.0, // 7
|
|
|
|
]);
|
|
|
|
|
|
|
|
const CUBE_INDICES: Uint16Array = new Uint16Array([
|
|
|
|
0, 1, 2, 2, 1, 3, // bottom
|
|
|
|
0, 5, 1, 0, 4, 5, // front
|
|
|
|
2, 4, 0, 2, 6, 4, // left
|
|
|
|
3, 5, 1, 3, 7, 5, // right
|
|
|
|
2, 7, 3, 2, 6, 7, // back
|
|
|
|
4, 5, 6, 6, 5, 7, // top
|
|
|
|
]);
|
2017-09-02 16:41:08 -04:00
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
const MONUMENT_NORMALS: glmatrix.vec4[] = [
|
|
|
|
glmatrix.vec4.clone([ 0.0, -1.0, 0.0, 1.0]),
|
|
|
|
glmatrix.vec4.clone([ 0.0, 0.0, -1.0, 1.0]),
|
|
|
|
glmatrix.vec4.clone([-1.0, 0.0, 0.0, 1.0]),
|
|
|
|
glmatrix.vec4.clone([ 1.0, 0.0, 0.0, 1.0]),
|
|
|
|
glmatrix.vec4.clone([ 0.0, 0.0, 1.0, 1.0]),
|
|
|
|
glmatrix.vec4.clone([ 0.0, 1.0, 0.0, 1.0]),
|
|
|
|
];
|
|
|
|
|
2017-08-31 20:08:22 -04:00
|
|
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
|
|
|
none: NoAAStrategy,
|
|
|
|
ssaa: SSAAStrategy,
|
|
|
|
};
|
|
|
|
|
|
|
|
interface AntialiasingStrategyTable {
|
|
|
|
none: typeof NoAAStrategy;
|
|
|
|
ssaa: typeof SSAAStrategy;
|
|
|
|
}
|
2017-08-31 19:11:09 -04:00
|
|
|
|
2017-09-07 22:01:55 -04:00
|
|
|
interface TextLine {
|
|
|
|
names: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
interface MonumentSide {
|
|
|
|
lines: TextLine[];
|
2017-09-07 01:50:07 -04:00
|
|
|
}
|
|
|
|
|
2017-10-03 18:24:56 -04:00
|
|
|
interface MeshDescriptor {
|
|
|
|
glyphID: number;
|
|
|
|
textFrameIndex: number;
|
|
|
|
positions: glmatrix.vec2[];
|
|
|
|
}
|
|
|
|
|
2018-03-09 18:55:46 -05:00
|
|
|
function F32ArrayToMat4(array: Float32Array): mat4 {
|
|
|
|
const mat = glmatrix.mat4.create();
|
|
|
|
glmatrix.mat4.set(mat, array[0], array[1], array[2], array[3],
|
|
|
|
array[4], array[5], array[6], array[7],
|
|
|
|
array[8], array[9], array[10], array[11],
|
|
|
|
array[12], array[13], array[14], array[15]);
|
|
|
|
return mat;
|
|
|
|
}
|
|
|
|
|
2017-09-01 21:11:44 -04:00
|
|
|
class ThreeDController extends DemoAppController<ThreeDView> {
|
2018-02-19 14:52:00 -05:00
|
|
|
font!: PathfinderFont;
|
|
|
|
textFrames!: TextFrame[];
|
|
|
|
glyphStore!: GlyphStore;
|
|
|
|
meshDescriptors!: MeshDescriptor[];
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
atlasGlyphs!: AtlasGlyph[];
|
|
|
|
atlas!: Atlas;
|
2017-10-17 18:30:33 -04:00
|
|
|
|
2018-03-07 17:06:54 -05:00
|
|
|
baseMeshes!: PathfinderMeshPack;
|
|
|
|
private expandedMeshes!: PathfinderPackedMeshes[];
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
private monumentPromise!: Promise<MonumentSide[]>;
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-08-31 22:19:26 -04:00
|
|
|
start() {
|
|
|
|
super.start();
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
this.atlas = new Atlas;
|
|
|
|
|
2017-09-07 22:01:55 -04:00
|
|
|
this.monumentPromise = window.fetch(TEXT_DATA_URI)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(textData => this.parseTextData(textData));
|
2017-09-07 01:50:07 -04:00
|
|
|
|
2017-09-19 23:19:53 -04:00
|
|
|
this.loadInitialFile(this.builtinFileURI);
|
2017-08-31 22:19:26 -04:00
|
|
|
}
|
|
|
|
|
2017-10-02 22:58:38 -04:00
|
|
|
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
2017-10-17 18:30:33 -04:00
|
|
|
this.font = new PathfinderFont(fileData, builtinName);
|
|
|
|
this.monumentPromise.then(monument => this.layoutMonument(fileData, monument));
|
2017-09-28 17:34:48 -04:00
|
|
|
}
|
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
protected createView(areaLUT: HTMLImageElement,
|
|
|
|
gammaLUT: HTMLImageElement,
|
2017-11-20 19:47:52 -05:00
|
|
|
commonShaderSource: string,
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>):
|
|
|
|
ThreeDView {
|
2018-03-09 12:20:27 -05:00
|
|
|
return new ThreeDView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
|
2017-09-28 17:34:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected get builtinFileURI(): string {
|
|
|
|
return BUILTIN_FONT_URI;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get defaultFile(): string {
|
|
|
|
return FONT;
|
|
|
|
}
|
|
|
|
|
2017-09-07 22:01:55 -04:00
|
|
|
private parseTextData(textData: any): MonumentSide[] {
|
|
|
|
const sides = [];
|
|
|
|
for (let sideIndex = 0; sideIndex < 4; sideIndex++)
|
|
|
|
sides[sideIndex] = { upper: { lines: [] }, lower: { lines: [] } };
|
2017-09-07 01:50:07 -04:00
|
|
|
|
|
|
|
for (const nameData of textData.monument) {
|
2017-09-28 17:34:48 -04:00
|
|
|
const side = parseInt(nameData.side, 10) - 1;
|
|
|
|
const row = parseInt(nameData.row, 10) - 1;
|
|
|
|
const index = parseInt(nameData.number, 10) - 1;
|
2017-09-07 22:01:55 -04:00
|
|
|
|
|
|
|
if (sides[side] == null)
|
2017-09-07 01:50:07 -04:00
|
|
|
continue;
|
|
|
|
|
2017-09-07 22:01:55 -04:00
|
|
|
const lines: TextLine[] = sides[side][nameData.panel as ('upper' | 'lower')].lines;
|
|
|
|
if (lines[row] == null)
|
|
|
|
lines[row] = { names: [] };
|
2017-09-07 01:50:07 -04:00
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
lines[row].names[index] = nameData.name;
|
2017-09-07 01:50:07 -04:00
|
|
|
}
|
|
|
|
|
2017-09-07 22:01:55 -04:00
|
|
|
return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) }));
|
2017-09-07 01:50:07 -04:00
|
|
|
}
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
private layoutMonument(fileData: ArrayBuffer, monument: MonumentSide[]) {
|
2017-09-27 16:02:32 -04:00
|
|
|
this.textFrames = [];
|
|
|
|
let glyphsNeeded: number[] = [];
|
|
|
|
|
2017-09-07 22:01:55 -04:00
|
|
|
for (const monumentSide of monument) {
|
2017-09-28 17:34:48 -04:00
|
|
|
const textRuns = [];
|
2017-09-07 22:01:55 -04:00
|
|
|
for (let lineNumber = 0; lineNumber < monumentSide.lines.length; lineNumber++) {
|
|
|
|
const line = monumentSide.lines[lineNumber];
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
const lineY = -lineNumber * this.font.opentypeFont.lineHeight();
|
2017-09-28 17:34:48 -04:00
|
|
|
const lineGlyphs = line.names.map(name => {
|
2017-10-17 18:30:33 -04:00
|
|
|
const glyphs = this.font.opentypeFont.stringToGlyphs(name);
|
2017-09-27 16:02:32 -04:00
|
|
|
const glyphIDs = glyphs.map(glyph => (glyph as any).index);
|
|
|
|
const width = _.sumBy(glyphs, glyph => glyph.advanceWidth);
|
|
|
|
return { glyphs: glyphIDs, width: width };
|
2017-09-07 22:01:55 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
const usedSpace = _.sumBy(lineGlyphs, 'width');
|
2017-09-12 22:43:43 -04:00
|
|
|
const emptySpace = Math.max(TEXT_AVAILABLE_WIDTH - usedSpace, 0.0);
|
2017-09-07 22:01:55 -04:00
|
|
|
const spacing = emptySpace / Math.max(lineGlyphs.length - 1, 1);
|
|
|
|
|
|
|
|
let currentX = 0.0;
|
|
|
|
for (const glyphInfo of lineGlyphs) {
|
|
|
|
const textRunOrigin = [currentX, lineY];
|
2017-10-17 18:30:33 -04:00
|
|
|
const textRun = new TextRun(glyphInfo.glyphs, textRunOrigin, this.font);
|
2017-09-27 16:02:32 -04:00
|
|
|
textRun.layout();
|
|
|
|
textRuns.push(textRun);
|
2017-09-07 22:01:55 -04:00
|
|
|
currentX += glyphInfo.width + spacing;
|
|
|
|
}
|
2017-09-07 01:11:32 -04:00
|
|
|
}
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
const textFrame = new TextFrame(textRuns, this.font);
|
2017-09-27 16:02:32 -04:00
|
|
|
this.textFrames.push(textFrame);
|
|
|
|
glyphsNeeded.push(...textFrame.allGlyphIDs);
|
2017-09-07 22:01:55 -04:00
|
|
|
}
|
2017-09-07 19:13:55 -04:00
|
|
|
|
2017-09-27 16:02:32 -04:00
|
|
|
glyphsNeeded.sort((a, b) => a - b);
|
|
|
|
glyphsNeeded = _.sortedUniq(glyphsNeeded);
|
2017-09-07 01:11:32 -04:00
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
this.glyphStore = new GlyphStore(this.font, glyphsNeeded);
|
2017-09-27 16:02:32 -04:00
|
|
|
this.glyphStore.partition().then(result => {
|
2017-10-17 18:30:33 -04:00
|
|
|
// Build the atlas glyphs needed.
|
|
|
|
this.atlasGlyphs = [];
|
|
|
|
for (const glyphID of glyphsNeeded) {
|
|
|
|
const glyphKey = new GlyphKey(glyphID, null);
|
|
|
|
const glyphStoreIndex = this.glyphStore.indexOfGlyphWithID(glyphID);
|
|
|
|
if (glyphStoreIndex != null)
|
|
|
|
this.atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey));
|
|
|
|
}
|
|
|
|
|
2017-10-03 18:24:56 -04:00
|
|
|
const hint = new Hint(this.glyphStore.font, PIXELS_PER_UNIT, false);
|
|
|
|
|
2017-09-26 18:38:50 -04:00
|
|
|
this.baseMeshes = result.meshes;
|
2017-10-03 18:24:56 -04:00
|
|
|
|
|
|
|
this.meshDescriptors = [];
|
|
|
|
|
|
|
|
for (let textFrameIndex = 0;
|
|
|
|
textFrameIndex < this.textFrames.length;
|
|
|
|
textFrameIndex++) {
|
|
|
|
const textFrame = this.textFrames[textFrameIndex];
|
2017-11-30 17:02:57 -05:00
|
|
|
const textBounds = textFrame.bounds;
|
2017-10-03 18:24:56 -04:00
|
|
|
|
|
|
|
let glyphDescriptors = [];
|
|
|
|
for (const run of textFrame.runs) {
|
|
|
|
for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) {
|
|
|
|
glyphDescriptors.push({
|
|
|
|
glyphID: run.glyphIDs[glyphIndex],
|
|
|
|
position: run.calculatePixelOriginForGlyphAt(glyphIndex,
|
|
|
|
PIXELS_PER_UNIT,
|
2017-11-30 17:02:57 -05:00
|
|
|
0.0,
|
|
|
|
hint,
|
|
|
|
textBounds),
|
2017-10-03 18:24:56 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
glyphDescriptors = _.sortBy(glyphDescriptors, descriptor => descriptor.glyphID);
|
|
|
|
|
|
|
|
let currentMeshDescriptor: (MeshDescriptor | null) = null;
|
|
|
|
for (const glyphDescriptor of glyphDescriptors) {
|
|
|
|
if (currentMeshDescriptor == null ||
|
|
|
|
glyphDescriptor.glyphID !== currentMeshDescriptor.glyphID) {
|
|
|
|
if (currentMeshDescriptor != null)
|
|
|
|
this.meshDescriptors.push(currentMeshDescriptor);
|
|
|
|
currentMeshDescriptor = {
|
|
|
|
glyphID: glyphDescriptor.glyphID,
|
|
|
|
positions: [],
|
|
|
|
textFrameIndex: textFrameIndex,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
currentMeshDescriptor.positions.push(glyphDescriptor.position);
|
|
|
|
}
|
|
|
|
if (currentMeshDescriptor != null)
|
|
|
|
this.meshDescriptors.push(currentMeshDescriptor);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.expandedMeshes = this.meshDescriptors.map(meshDescriptor => {
|
|
|
|
const glyphIndex = _.sortedIndexOf(glyphsNeeded, meshDescriptor.glyphID);
|
2018-03-07 17:06:54 -05:00
|
|
|
return new PathfinderPackedMeshes(this.baseMeshes, [glyphIndex + 1]);
|
2017-09-27 16:02:32 -04:00
|
|
|
});
|
2017-10-03 18:24:56 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
this.view.then(view => view.attachMeshes(this.expandedMeshes));
|
2017-08-31 20:08:22 -04:00
|
|
|
});
|
2017-08-31 19:11:09 -04:00
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
}
|
2017-08-31 19:11:09 -04:00
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
class ThreeDView extends DemoView implements TextRenderContext {
|
|
|
|
cameraView: CameraView;
|
|
|
|
|
|
|
|
get atlas(): Atlas {
|
|
|
|
return this.appController.atlas;
|
|
|
|
}
|
|
|
|
|
|
|
|
get atlasGlyphs(): AtlasGlyph[] {
|
|
|
|
return this.appController.atlasGlyphs;
|
|
|
|
}
|
|
|
|
|
|
|
|
set atlasGlyphs(newAtlasGlyphs: AtlasGlyph[]) {
|
|
|
|
this.appController.atlasGlyphs = newAtlasGlyphs;
|
|
|
|
}
|
|
|
|
|
|
|
|
get glyphStore(): GlyphStore {
|
|
|
|
return this.appController.glyphStore;
|
|
|
|
}
|
|
|
|
|
|
|
|
get font(): PathfinderFont {
|
|
|
|
return this.appController.font;
|
|
|
|
}
|
|
|
|
|
|
|
|
get fontSize(): number {
|
|
|
|
return ATLAS_FONT_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
get useHinting(): boolean {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
get atlasPixelsPerUnit(): number {
|
|
|
|
return ATLAS_FONT_SIZE / this.font.opentypeFont.unitsPerEm;
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
renderer: ThreeDRenderer;
|
|
|
|
|
|
|
|
appController: ThreeDController;
|
|
|
|
|
|
|
|
protected get camera(): PerspectiveCamera {
|
|
|
|
return this.renderer.camera;
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(appController: ThreeDController,
|
2018-03-09 12:20:27 -05:00
|
|
|
areaLUT: HTMLImageElement,
|
2017-11-07 17:13:13 -05:00
|
|
|
gammaLUT: HTMLImageElement,
|
2017-10-16 22:29:13 -04:00
|
|
|
commonShaderSource: string,
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
2018-03-09 12:20:27 -05:00
|
|
|
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
|
2017-10-16 22:29:13 -04:00
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
this.cameraView = new ThreeDAtlasCameraView;
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
this.appController = appController;
|
|
|
|
this.renderer = new ThreeDRenderer(this);
|
|
|
|
|
|
|
|
this.resizeToFit(true);
|
|
|
|
}
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
newTimingsReceived(timings: Timings): void {}
|
2017-10-16 22:29:13 -04:00
|
|
|
}
|
|
|
|
|
2017-12-05 22:08:31 -05:00
|
|
|
class ThreeDRenderer extends Renderer {
|
2018-02-19 14:52:00 -05:00
|
|
|
renderContext!: ThreeDView;
|
2017-08-31 20:08:22 -04:00
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
camera: PerspectiveCamera;
|
2017-08-31 20:08:22 -04:00
|
|
|
|
2018-02-05 18:10:52 -05:00
|
|
|
needsStencil: boolean = false;
|
|
|
|
|
2018-01-02 22:15:19 -05:00
|
|
|
get isMulticolor(): boolean {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get destFramebuffer(): WebGLFramebuffer | null {
|
|
|
|
return null;
|
|
|
|
}
|
2017-08-31 19:11:09 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get destAllocatedSize(): glmatrix.vec2 {
|
|
|
|
return glmatrix.vec2.clone([
|
|
|
|
this.renderContext.canvas.width,
|
|
|
|
this.renderContext.canvas.height,
|
|
|
|
]);
|
|
|
|
}
|
2017-09-06 17:11:58 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get destUsedSize(): glmatrix.vec2 {
|
|
|
|
return this.destAllocatedSize;
|
|
|
|
}
|
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
get allowSubpixelAA(): boolean {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-11-01 19:09:58 -04:00
|
|
|
get backgroundColor(): glmatrix.vec4 {
|
|
|
|
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
|
2017-10-16 22:29:13 -04:00
|
|
|
}
|
2017-09-07 01:50:07 -04:00
|
|
|
|
2017-10-03 18:24:56 -04:00
|
|
|
protected get pathIDsAreInstanced(): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
protected get worldTransform() {
|
|
|
|
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
protected get usedSizeFactor(): glmatrix.vec2 {
|
|
|
|
return glmatrix.vec2.clone([1.0, 1.0]);
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-11-01 19:09:58 -04:00
|
|
|
protected get objectCount(): number {
|
2018-03-07 17:06:54 -05:00
|
|
|
return this.meshBuffers == null ? 0 : this.meshBuffers.length;
|
2017-11-01 19:09:58 -04:00
|
|
|
}
|
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
private cubeVertexPositionBuffer: WebGLBuffer;
|
|
|
|
private cubeIndexBuffer: WebGLBuffer;
|
2017-10-18 22:16:56 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
private glyphPositionsBuffer!: WebGLBuffer;
|
2017-10-17 18:30:33 -04:00
|
|
|
private glyphPositions: number[];
|
|
|
|
private glyphPositionRanges: Range[];
|
|
|
|
private glyphTexCoords: glmatrix.vec4[];
|
|
|
|
private glyphSizes: glmatrix.vec2[];
|
2017-08-31 19:11:09 -04:00
|
|
|
|
2017-10-18 22:16:56 -04:00
|
|
|
private distantGlyphVAO: WebGLVertexArrayObjectOES | null;
|
|
|
|
|
2018-03-09 18:55:46 -05:00
|
|
|
private vrProjectionMatrix: Float32Array | null;
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
constructor(renderContext: ThreeDView) {
|
|
|
|
super(renderContext);
|
2017-09-02 15:14:10 -04:00
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
const gl = renderContext.gl;
|
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
this.glyphPositions = [];
|
|
|
|
this.glyphPositionRanges = [];
|
|
|
|
this.glyphTexCoords = [];
|
|
|
|
this.glyphSizes = [];
|
|
|
|
|
|
|
|
this.distantGlyphVAO = null;
|
2018-03-09 18:55:46 -05:00
|
|
|
this.vrProjectionMatrix = null;
|
2017-10-16 22:29:13 -04:00
|
|
|
this.camera = new PerspectiveCamera(renderContext.canvas, {
|
2017-09-14 21:48:55 -04:00
|
|
|
innerCollisionExtent: MONUMENT_SCALE[0],
|
|
|
|
});
|
2017-10-16 22:29:13 -04:00
|
|
|
this.camera.onChange = () => renderContext.setDirty();
|
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
this.cubeVertexPositionBuffer = unwrapNull(gl.createBuffer());
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
|
|
|
gl.bufferData(gl.ARRAY_BUFFER, CUBE_VERTEX_POSITIONS, gl.STATIC_DRAW);
|
2017-10-16 22:29:13 -04:00
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
this.cubeIndexBuffer = unwrapNull(gl.createBuffer());
|
|
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
|
|
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, gl.STATIC_DRAW);
|
2017-10-16 22:29:13 -04:00
|
|
|
}
|
2017-09-09 03:04:35 -04:00
|
|
|
|
2018-03-07 17:06:54 -05:00
|
|
|
attachMeshes(expandedMeshes: PathfinderPackedMeshes[]) {
|
2017-10-16 22:29:13 -04:00
|
|
|
super.attachMeshes(expandedMeshes);
|
2017-09-09 03:04:35 -04:00
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
this.renderAtlasGlyphs(this.renderContext.appController.atlasGlyphs);
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
this.uploadPathColors(expandedMeshes.length);
|
|
|
|
this.uploadPathTransforms(expandedMeshes.length);
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
this.uploadGlyphPositions();
|
2017-08-31 20:08:22 -04:00
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
pathCountForObject(objectIndex: number): number {
|
|
|
|
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
|
2017-10-05 22:14:52 -04:00
|
|
|
}
|
|
|
|
|
2017-10-09 17:14:24 -04:00
|
|
|
pathBoundingRects(objectIndex: number): Float32Array {
|
2017-10-16 22:29:13 -04:00
|
|
|
panic("ThreeDRenderer.pathBoundingRects(): TODO");
|
2017-10-09 17:14:24 -04:00
|
|
|
return glmatrix.vec4.create();
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
setHintsUniform(uniforms: UniformMap): void {
|
|
|
|
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
2018-03-09 18:55:46 -05:00
|
|
|
redrawVR(frame: VRFrameData): void {
|
|
|
|
this.vrProjectionMatrix = frame.leftProjectionMatrix;
|
|
|
|
this.camera.setView(F32ArrayToMat4(frame.leftViewMatrix), frame.pose);
|
|
|
|
this.redraw();
|
|
|
|
this.vrProjectionMatrix = frame.rightProjectionMatrix;
|
|
|
|
this.camera.setView(F32ArrayToMat4(frame.rightViewMatrix), frame.pose);
|
|
|
|
this.redraw();
|
|
|
|
}
|
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
|
|
|
|
const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
|
|
|
|
const pathCount = this.pathCountForObject(objectIndex);
|
|
|
|
const pathTransforms = this.createPathTransformBuffers(pathCount);
|
|
|
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
|
|
|
const glyphOrigin = meshDescriptor.positions[pathIndex];
|
|
|
|
pathTransforms.st.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4);
|
|
|
|
}
|
|
|
|
return pathTransforms;
|
|
|
|
}
|
|
|
|
|
2017-11-01 19:09:58 -04:00
|
|
|
protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
protected drawSceneryIfNecessary(): void {
|
2017-10-17 18:30:33 -04:00
|
|
|
const gl = this.renderContext.gl;
|
2017-10-16 22:29:13 -04:00
|
|
|
|
2017-11-01 19:09:58 -04:00
|
|
|
// Set up the depth buffer for drawing the monument.
|
|
|
|
gl.clearDepth(1.0);
|
|
|
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
this.drawMonument();
|
2017-10-16 22:29:13 -04:00
|
|
|
|
|
|
|
// Clear to avoid Z-fighting.
|
2017-10-17 18:30:33 -04:00
|
|
|
gl.clearDepth(1.0);
|
|
|
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
|
|
|
|
|
|
this.drawDistantGlyphs();
|
2017-11-01 19:09:58 -04:00
|
|
|
|
|
|
|
// Set up the depth buffer for direct rendering.
|
|
|
|
gl.clearDepth(0.0);
|
|
|
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
2017-10-09 17:14:24 -04:00
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
protected compositeIfNecessary(): void {}
|
|
|
|
|
2017-10-03 18:24:56 -04:00
|
|
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
|
|
|
return TEXT_COLOR;
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
2017-09-07 22:01:55 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
protected meshInstanceCountForObject(objectIndex: number): number {
|
|
|
|
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
|
|
|
|
}
|
|
|
|
|
2017-09-06 19:32:11 -04:00
|
|
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
|
|
|
aaLevel: number,
|
2017-09-30 01:12:09 -04:00
|
|
|
subpixelAA: SubpixelAAType):
|
2017-08-31 19:11:09 -04:00
|
|
|
AntialiasingStrategy {
|
2017-10-09 17:14:24 -04:00
|
|
|
if (aaType !== 'xcaa')
|
2017-09-06 19:32:11 -04:00
|
|
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
2017-08-31 20:08:22 -04:00
|
|
|
throw new PathfinderError("Unsupported antialiasing type!");
|
2017-08-31 19:11:09 -04:00
|
|
|
}
|
|
|
|
|
2017-11-01 19:09:58 -04:00
|
|
|
protected clearDestFramebuffer(): void {
|
2017-10-17 18:30:33 -04:00
|
|
|
const gl = this.renderContext.gl;
|
2017-11-01 19:09:58 -04:00
|
|
|
|
|
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.destFramebuffer);
|
|
|
|
gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]);
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
|
|
gl.clearDepth(1.0);
|
|
|
|
gl.depthMask(true);
|
|
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
2017-09-28 17:34:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
|
2017-10-16 22:29:13 -04:00
|
|
|
const textFrameIndex = this.renderContext
|
|
|
|
.appController
|
|
|
|
.meshDescriptors[objectIndex]
|
|
|
|
.textFrameIndex;
|
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
const transform = glmatrix.mat4.create();
|
2017-10-03 18:24:56 -04:00
|
|
|
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex);
|
2017-09-28 17:34:48 -04:00
|
|
|
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
|
|
|
|
return transform;
|
|
|
|
}
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
protected instanceRangeForObject(glyphDescriptorIndex: number): Range {
|
|
|
|
if (!this.objectIsVisible(glyphDescriptorIndex))
|
|
|
|
return new Range(0, 0);
|
|
|
|
|
|
|
|
const totalLength =
|
|
|
|
this.renderContext.appController.meshDescriptors[glyphDescriptorIndex].positions.length;
|
|
|
|
|
|
|
|
const cameraTransform = this.calculateCameraTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
|
|
|
const worldTransform = this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
|
|
|
const glyphsTransform = glmatrix.mat4.clone(cameraTransform);
|
|
|
|
const renderTransform = glmatrix.mat4.clone(worldTransform);
|
|
|
|
const modelviewTransform = this.getModelviewTransform(glyphDescriptorIndex);
|
|
|
|
glmatrix.mat4.mul(glyphsTransform, cameraTransform, modelviewTransform);
|
|
|
|
glmatrix.mat4.mul(renderTransform, renderTransform, modelviewTransform);
|
|
|
|
|
|
|
|
const nearbyRange = this.findNearbyGlyphPositions(glyphsTransform, glyphDescriptorIndex);
|
|
|
|
const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex];
|
|
|
|
nearbyRange.start -= glyphPositionRange.start;
|
|
|
|
nearbyRange.end -= glyphPositionRange.start;
|
|
|
|
return nearbyRange;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected newTimingsReceived(): void {
|
2017-10-23 12:40:27 -04:00
|
|
|
const newTimings: Partial<Timings> = _.pick(this.lastTimings, ['rendering']);
|
2017-10-17 18:30:33 -04:00
|
|
|
this.renderContext.appController.newTimingsReceived(newTimings);
|
|
|
|
}
|
|
|
|
|
2017-10-31 15:41:38 -04:00
|
|
|
protected directCurveProgramName(): keyof ShaderMap<void> {
|
2017-10-30 16:34:55 -04:00
|
|
|
return 'direct3DCurve';
|
|
|
|
}
|
|
|
|
|
2017-10-31 15:41:38 -04:00
|
|
|
protected directInteriorProgramName(): keyof ShaderMap<void> {
|
2017-10-30 16:34:55 -04:00
|
|
|
return 'direct3DInterior';
|
|
|
|
}
|
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
// Cheap but effective backface culling.
|
2017-10-17 18:30:33 -04:00
|
|
|
private objectIsVisible(objectIndex: number): boolean {
|
2017-10-16 22:29:13 -04:00
|
|
|
const textFrameIndex = this.renderContext
|
|
|
|
.appController
|
|
|
|
.meshDescriptors[objectIndex]
|
|
|
|
.textFrameIndex;
|
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
const translation = this.camera.translation;
|
|
|
|
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
|
2017-10-03 18:24:56 -04:00
|
|
|
switch (textFrameIndex) {
|
2017-09-28 17:34:48 -04:00
|
|
|
case 0: return translation[2] < -extent;
|
|
|
|
case 1: return translation[0] < -extent;
|
|
|
|
case 2: return translation[2] > extent;
|
|
|
|
default: return translation[0] > extent;
|
|
|
|
}
|
|
|
|
}
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
private uploadGlyphPositions(): void {
|
|
|
|
const gl = this.renderContext.gl;
|
|
|
|
const font = this.renderContext.font;
|
|
|
|
const meshDescriptors = this.renderContext.appController.meshDescriptors;
|
|
|
|
|
|
|
|
this.glyphPositions = [];
|
|
|
|
this.glyphPositionRanges = [];
|
|
|
|
for (const meshDescriptor of meshDescriptors) {
|
|
|
|
const glyphIndex = this.renderContext.atlasGlyphs.findIndex(atlasGlyph => {
|
|
|
|
return atlasGlyph.glyphKey.id === meshDescriptor.glyphID;
|
|
|
|
});
|
|
|
|
const glyph = this.renderContext.atlasGlyphs[glyphIndex];
|
|
|
|
const glyphMetrics = unwrapNull(font.metricsForGlyph(glyph.glyphKey.id));
|
2017-11-30 17:02:57 -05:00
|
|
|
const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create());
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
const firstPosition = this.glyphPositions.length / 2;
|
|
|
|
|
|
|
|
for (const position of meshDescriptor.positions) {
|
|
|
|
this.glyphPositions.push(position[0] + glyphUnitMetrics.left,
|
|
|
|
position[1] + glyphUnitMetrics.descent);
|
|
|
|
}
|
|
|
|
|
|
|
|
const lastPosition = this.glyphPositions.length / 2;
|
|
|
|
this.glyphPositionRanges.push(new Range(firstPosition, lastPosition));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.glyphPositionsBuffer = unwrapNull(gl.createBuffer());
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
|
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.glyphPositions), gl.STATIC_DRAW);
|
|
|
|
}
|
|
|
|
|
|
|
|
private drawMonument(): void {
|
2017-11-03 21:49:25 -04:00
|
|
|
const renderContext = this.renderContext;
|
|
|
|
const gl = renderContext.gl;
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
// Set up the cube VBO.
|
|
|
|
const monumentProgram = this.renderContext.shaderPrograms.demo3DMonument;
|
2017-11-03 21:49:25 -04:00
|
|
|
gl.useProgram(monumentProgram.program);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
|
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
|
|
|
gl.vertexAttribPointer(monumentProgram.attributes.aPosition, 3, gl.FLOAT, false, 0, 0);
|
|
|
|
gl.enableVertexAttribArray(monumentProgram.attributes.aPosition);
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
// Set uniforms for the monument.
|
2017-11-03 21:49:25 -04:00
|
|
|
const projection = this.calculateProjectionTransform();
|
|
|
|
const modelview = this.calculateModelviewTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE);
|
|
|
|
gl.uniformMatrix4fv(monumentProgram.uniforms.uProjection, false, projection);
|
|
|
|
gl.uniformMatrix4fv(monumentProgram.uniforms.uModelview, false, modelview);
|
|
|
|
const cameraModelview = this.calculateCameraModelviewTransform();
|
|
|
|
const lightPosition = glmatrix.vec4.clone([-1750.0, -700.0, 1750.0, 1.0]);
|
|
|
|
glmatrix.vec4.transformMat4(lightPosition, lightPosition, cameraModelview);
|
|
|
|
gl.uniform3f(monumentProgram.uniforms.uLightPosition,
|
|
|
|
lightPosition[0] / lightPosition[3],
|
|
|
|
lightPosition[1] / lightPosition[3],
|
|
|
|
lightPosition[2] / lightPosition[3]);
|
|
|
|
gl.uniform3fv(monumentProgram.uniforms.uAmbientColor, AMBIENT_COLOR);
|
|
|
|
gl.uniform3fv(monumentProgram.uniforms.uDiffuseColor, DIFFUSE_COLOR);
|
|
|
|
gl.uniform3fv(monumentProgram.uniforms.uSpecularColor, SPECULAR_COLOR);
|
|
|
|
gl.uniform1f(monumentProgram.uniforms.uShininess, MONUMENT_SHININESS);
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
// Set state for the monument.
|
|
|
|
gl.enable(gl.DEPTH_TEST);
|
2017-11-01 19:09:58 -04:00
|
|
|
gl.depthFunc(gl.LESS);
|
2017-10-17 18:30:33 -04:00
|
|
|
gl.depthMask(true);
|
|
|
|
gl.disable(gl.SCISSOR_TEST);
|
|
|
|
gl.disable(gl.BLEND);
|
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
// Loop over each face.
|
|
|
|
for (let face = 0; face < 6; face++) {
|
|
|
|
// Set the uniforms for this face.
|
|
|
|
const normal = glmatrix.vec4.clone(MONUMENT_NORMALS[face]);
|
|
|
|
glmatrix.vec4.transformMat4(normal, normal, this.camera.rotationMatrix);
|
|
|
|
gl.uniform3f(monumentProgram.uniforms.uNormal,
|
|
|
|
normal[0] / normal[3],
|
|
|
|
normal[1] / normal[3],
|
|
|
|
normal[2] / normal[3]);
|
|
|
|
|
|
|
|
// Draw the face!
|
|
|
|
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, face * 6 * UINT16_SIZE);
|
|
|
|
}
|
2017-10-17 18:30:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private drawDistantGlyphs(): void {
|
|
|
|
const appController = this.renderContext.appController;
|
|
|
|
const gl = this.renderContext.gl;
|
|
|
|
|
|
|
|
// Prepare the distant glyph VAO.
|
2017-10-18 22:16:56 -04:00
|
|
|
if (this.distantGlyphVAO == null)
|
|
|
|
this.distantGlyphVAO = this.renderContext.vertexArrayObjectExt.createVertexArrayOES();
|
|
|
|
this.renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.distantGlyphVAO);
|
2017-10-17 18:30:33 -04:00
|
|
|
const distantGlyphProgram = this.renderContext.shaderPrograms.demo3DDistantGlyph;
|
|
|
|
gl.useProgram(distantGlyphProgram.program);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.renderContext.quadPositionsBuffer);
|
|
|
|
gl.vertexAttribPointer(distantGlyphProgram.attributes.aQuadPosition,
|
|
|
|
2,
|
|
|
|
gl.FLOAT,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
0);
|
|
|
|
gl.enableVertexAttribArray(distantGlyphProgram.attributes.aQuadPosition);
|
|
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.renderContext.quadElementsBuffer);
|
|
|
|
|
|
|
|
// Set global uniforms.
|
|
|
|
gl.uniform4fv(distantGlyphProgram.uniforms.uColor,
|
|
|
|
_.map(TEXT_COLOR, number => number / 0xff));
|
|
|
|
const atlasTexture = this.renderContext.atlas.ensureTexture(this.renderContext);
|
|
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, atlasTexture);
|
|
|
|
gl.uniform1i(distantGlyphProgram.uniforms.uAtlas, 0);
|
|
|
|
|
|
|
|
// Set state.
|
|
|
|
gl.disable(gl.DEPTH_TEST);
|
|
|
|
gl.disable(gl.SCISSOR_TEST);
|
|
|
|
gl.enable(gl.BLEND);
|
|
|
|
gl.blendEquation(gl.FUNC_ADD);
|
2018-01-02 22:15:19 -05:00
|
|
|
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
// Draw textures for distant glyphs.
|
|
|
|
const cameraTransform = this.calculateCameraTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
|
|
|
const worldTransform = this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
|
|
|
|
|
|
|
for (let glyphDescriptorIndex = 0;
|
|
|
|
glyphDescriptorIndex < this.glyphPositionRanges.length;
|
|
|
|
glyphDescriptorIndex++) {
|
|
|
|
if (!this.objectIsVisible(glyphDescriptorIndex))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const meshDescriptor = appController.meshDescriptors[glyphDescriptorIndex];
|
|
|
|
const glyphIndex = this.renderContext.atlasGlyphs.findIndex(glyph => {
|
|
|
|
return glyph.glyphKey.id === meshDescriptor.glyphID;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Calculate transforms.
|
|
|
|
const glyphsTransform = glmatrix.mat4.clone(cameraTransform);
|
|
|
|
const renderTransform = glmatrix.mat4.clone(worldTransform);
|
|
|
|
const modelviewTransform = this.getModelviewTransform(glyphDescriptorIndex);
|
|
|
|
glmatrix.mat4.mul(glyphsTransform, cameraTransform, modelviewTransform);
|
|
|
|
glmatrix.mat4.mul(renderTransform, renderTransform, modelviewTransform);
|
|
|
|
|
|
|
|
const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex];
|
|
|
|
const nearbyGlyphPositionRange = this.findNearbyGlyphPositions(glyphsTransform,
|
|
|
|
glyphDescriptorIndex);
|
|
|
|
|
|
|
|
// Set uniforms.
|
|
|
|
gl.uniformMatrix4fv(distantGlyphProgram.uniforms.uTransform, false, renderTransform);
|
|
|
|
|
|
|
|
const glyphTexCoords = this.glyphTexCoords[glyphIndex];
|
|
|
|
gl.uniform4f(distantGlyphProgram.uniforms.uGlyphTexCoords,
|
|
|
|
glyphTexCoords[0],
|
|
|
|
glyphTexCoords[1],
|
|
|
|
glyphTexCoords[2],
|
|
|
|
glyphTexCoords[3]);
|
|
|
|
const glyphSize = this.glyphSizes[glyphIndex];
|
|
|
|
gl.uniform2f(distantGlyphProgram.uniforms.uGlyphSize, glyphSize[0], glyphSize[1]);
|
|
|
|
|
|
|
|
const rangeBefore = new Range(glyphPositionRange.start,
|
|
|
|
nearbyGlyphPositionRange.start);
|
|
|
|
if (!rangeBefore.isEmpty) {
|
|
|
|
// Would be nice to have `glDrawElementsInstancedBaseInstance`...
|
|
|
|
// FIXME(pcwalton): Cache VAOs?
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
|
|
|
gl.vertexAttribPointer(distantGlyphProgram.attributes.aPosition,
|
|
|
|
2,
|
|
|
|
gl.FLOAT,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
rangeBefore.start * FLOAT32_SIZE * 2);
|
|
|
|
gl.enableVertexAttribArray(distantGlyphProgram.attributes.aPosition);
|
|
|
|
this.renderContext
|
|
|
|
.instancedArraysExt
|
|
|
|
.vertexAttribDivisorANGLE(distantGlyphProgram.attributes.aPosition, 1);
|
|
|
|
|
|
|
|
this.renderContext
|
|
|
|
.instancedArraysExt
|
|
|
|
.drawElementsInstancedANGLE(gl.TRIANGLES,
|
|
|
|
6,
|
|
|
|
gl.UNSIGNED_BYTE,
|
|
|
|
0,
|
|
|
|
rangeBefore.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
const rangeAfter = new Range(nearbyGlyphPositionRange.end, glyphPositionRange.end);
|
|
|
|
if (!rangeAfter.isEmpty) {
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
|
|
|
gl.vertexAttribPointer(distantGlyphProgram.attributes.aPosition,
|
|
|
|
2,
|
|
|
|
gl.FLOAT,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
rangeAfter.start * FLOAT32_SIZE * 2);
|
|
|
|
gl.enableVertexAttribArray(distantGlyphProgram.attributes.aPosition);
|
|
|
|
this.renderContext
|
|
|
|
.instancedArraysExt
|
|
|
|
.vertexAttribDivisorANGLE(distantGlyphProgram.attributes.aPosition, 1);
|
|
|
|
|
|
|
|
this.renderContext
|
|
|
|
.instancedArraysExt
|
|
|
|
.drawElementsInstancedANGLE(gl.TRIANGLES,
|
|
|
|
6,
|
|
|
|
gl.UNSIGNED_BYTE,
|
|
|
|
0,
|
|
|
|
rangeAfter.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
2017-08-31 20:08:22 -04:00
|
|
|
}
|
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
private calculateProjectionTransform(): glmatrix.mat4 {
|
2018-03-09 18:55:46 -05:00
|
|
|
if (this.vrProjectionMatrix != null) {
|
|
|
|
return F32ArrayToMat4(this.vrProjectionMatrix);
|
|
|
|
}
|
2017-10-16 22:29:13 -04:00
|
|
|
const canvas = this.renderContext.canvas;
|
2017-09-05 22:47:19 -04:00
|
|
|
const projection = glmatrix.mat4.create();
|
|
|
|
glmatrix.mat4.perspective(projection,
|
2017-09-02 16:41:08 -04:00
|
|
|
FOV / 180.0 * Math.PI,
|
2017-10-16 22:29:13 -04:00
|
|
|
canvas.width / canvas.height,
|
2017-09-02 16:41:08 -04:00
|
|
|
NEAR_CLIP_PLANE,
|
|
|
|
FAR_CLIP_PLANE);
|
2017-11-03 21:49:25 -04:00
|
|
|
return projection;
|
|
|
|
}
|
2017-09-05 22:47:19 -04:00
|
|
|
|
2017-11-03 21:49:25 -04:00
|
|
|
private calculateCameraModelviewTransform(): glmatrix.mat4 {
|
2017-09-05 22:47:19 -04:00
|
|
|
const modelview = glmatrix.mat4.create();
|
|
|
|
glmatrix.mat4.mul(modelview, modelview, this.camera.rotationMatrix);
|
|
|
|
glmatrix.mat4.translate(modelview, modelview, this.camera.translation);
|
2017-11-03 21:49:25 -04:00
|
|
|
return modelview;
|
|
|
|
}
|
|
|
|
|
|
|
|
private calculateModelviewTransform(modelviewTranslation: glmatrix.vec3,
|
|
|
|
modelviewScale: glmatrix.vec3):
|
|
|
|
glmatrix.mat4 {
|
|
|
|
const modelview = this.calculateCameraModelviewTransform();
|
2017-09-09 03:04:35 -04:00
|
|
|
glmatrix.mat4.translate(modelview, modelview, modelviewTranslation);
|
|
|
|
glmatrix.mat4.scale(modelview, modelview, modelviewScale);
|
2017-11-03 21:49:25 -04:00
|
|
|
return modelview;
|
|
|
|
}
|
|
|
|
|
|
|
|
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
|
|
|
modelviewScale: glmatrix.vec3):
|
|
|
|
glmatrix.mat4 {
|
|
|
|
const projection = this.calculateProjectionTransform();
|
|
|
|
const modelview = this.calculateModelviewTransform(modelviewTranslation, modelviewScale);
|
2017-09-05 22:47:19 -04:00
|
|
|
|
|
|
|
const transform = glmatrix.mat4.create();
|
|
|
|
glmatrix.mat4.mul(transform, projection, modelview);
|
2017-08-31 20:08:22 -04:00
|
|
|
return transform;
|
|
|
|
}
|
2017-10-17 18:30:33 -04:00
|
|
|
|
|
|
|
private calculateCameraTransform(modelviewTranslation: glmatrix.vec3,
|
|
|
|
modelviewScale: glmatrix.vec3):
|
|
|
|
glmatrix.mat4 {
|
|
|
|
const transform = glmatrix.mat4.create();
|
|
|
|
glmatrix.mat4.translate(transform, transform, this.camera.translation);
|
|
|
|
glmatrix.mat4.translate(transform, transform, modelviewTranslation);
|
|
|
|
glmatrix.mat4.scale(transform, transform, modelviewScale);
|
|
|
|
return transform;
|
|
|
|
}
|
|
|
|
|
|
|
|
private renderAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void {
|
|
|
|
const hint = new Hint(this.renderContext.font,
|
|
|
|
this.renderContext.atlasPixelsPerUnit,
|
|
|
|
false);
|
|
|
|
this.renderContext.atlas.layoutGlyphs(atlasGlyphs,
|
|
|
|
this.renderContext.font,
|
|
|
|
this.renderContext.atlasPixelsPerUnit,
|
2017-11-30 17:02:57 -05:00
|
|
|
0.0,
|
2017-10-17 18:30:33 -04:00
|
|
|
hint,
|
|
|
|
glmatrix.vec2.create());
|
|
|
|
|
|
|
|
const atlasRenderer = new ThreeDAtlasRenderer(this.renderContext, atlasGlyphs);
|
2018-03-07 17:06:54 -05:00
|
|
|
const baseMeshes = this.renderContext.appController.baseMeshes;
|
|
|
|
const expandedMeshes = new PathfinderPackedMeshes(baseMeshes);
|
|
|
|
atlasRenderer.attachMeshes([expandedMeshes]);
|
2017-10-17 18:30:33 -04:00
|
|
|
atlasRenderer.renderAtlas();
|
|
|
|
this.glyphTexCoords = atlasRenderer.glyphTexCoords;
|
|
|
|
this.glyphSizes = atlasRenderer.glyphSizes;
|
|
|
|
}
|
|
|
|
|
|
|
|
private findNearbyGlyphPositions(transform: glmatrix.mat4, glyphDescriptorIndex: number):
|
|
|
|
Range {
|
|
|
|
const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex];
|
|
|
|
const startPosition = this.findFirstGlyphPositionInRange(transform,
|
|
|
|
glyphPositionRange,
|
|
|
|
-MAX_DISTANCE);
|
|
|
|
const endPosition = this.findFirstGlyphPositionInRange(transform,
|
|
|
|
new Range(startPosition,
|
|
|
|
glyphPositionRange.end),
|
|
|
|
MAX_DISTANCE);
|
|
|
|
return new Range(startPosition, endPosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
private findFirstGlyphPositionInRange(transform: glmatrix.mat4,
|
|
|
|
range: Range,
|
|
|
|
maxDistance: number):
|
|
|
|
number {
|
|
|
|
let lo = range.start, hi = range.end;
|
|
|
|
while (lo < hi) {
|
|
|
|
const mid = lo + ((hi - lo) >> 1);
|
|
|
|
const glyphPosition = this.calculateTransformedGlyphPosition(transform, mid);
|
|
|
|
const glyphDistance = -glyphPosition[1];
|
|
|
|
if (glyphDistance < maxDistance)
|
|
|
|
lo = mid + 1;
|
|
|
|
else
|
|
|
|
hi = mid;
|
|
|
|
}
|
|
|
|
return lo;
|
|
|
|
}
|
|
|
|
|
|
|
|
private calculateTransformedGlyphPosition(transform: glmatrix.mat4,
|
|
|
|
glyphPositionIndex: number):
|
|
|
|
glmatrix.vec4 {
|
|
|
|
const position = glmatrix.vec4.clone([
|
|
|
|
this.glyphPositions[glyphPositionIndex * 2 + 0],
|
|
|
|
this.glyphPositions[glyphPositionIndex * 2 + 1],
|
|
|
|
0.0,
|
|
|
|
1.0,
|
|
|
|
]);
|
|
|
|
glmatrix.vec4.transformMat4(position, position, transform);
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ThreeDAtlasRenderer extends TextRenderer {
|
|
|
|
glyphTexCoords: glmatrix.vec4[];
|
|
|
|
glyphSizes: glmatrix.vec2[];
|
|
|
|
|
|
|
|
private allAtlasGlyphs: AtlasGlyph[];
|
|
|
|
|
2017-11-20 19:25:57 -05:00
|
|
|
get backgroundColor(): glmatrix.vec4 {
|
|
|
|
return glmatrix.vec4.create();
|
|
|
|
}
|
|
|
|
|
2017-10-17 18:30:33 -04:00
|
|
|
constructor(renderContext: ThreeDView, atlasGlyphs: AtlasGlyph[]) {
|
|
|
|
super(renderContext);
|
|
|
|
this.allAtlasGlyphs = atlasGlyphs;
|
2018-02-19 14:52:00 -05:00
|
|
|
this.glyphTexCoords = [];
|
|
|
|
this.glyphSizes = [];
|
2017-10-17 18:30:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
renderAtlas(): void {
|
|
|
|
this.createAtlasFramebuffer();
|
|
|
|
this.buildAtlasGlyphs(this.allAtlasGlyphs);
|
|
|
|
this.redraw();
|
|
|
|
this.calculateGlyphTexCoords();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected compositeIfNecessary(): void {}
|
|
|
|
|
|
|
|
private calculateGlyphTexCoords(): void {
|
2017-11-30 12:51:07 -05:00
|
|
|
const pixelsPerUnit = this.pixelsPerUnit;
|
2017-10-17 18:30:33 -04:00
|
|
|
const glyphCount = this.renderContext.atlasGlyphs.length;
|
|
|
|
const font = this.renderContext.font;
|
|
|
|
const hint = this.createHint();
|
|
|
|
|
|
|
|
this.glyphTexCoords = [];
|
|
|
|
this.glyphSizes = [];
|
|
|
|
|
|
|
|
for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
|
|
|
|
const glyph = this.renderContext.atlasGlyphs[glyphIndex];
|
2017-11-30 12:51:07 -05:00
|
|
|
const glyphPixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
|
2017-10-17 18:30:33 -04:00
|
|
|
const glyphMetrics = font.metricsForGlyph(glyph.glyphKey.id);
|
|
|
|
if (glyphMetrics == null)
|
|
|
|
continue;
|
|
|
|
|
2017-11-30 17:02:57 -05:00
|
|
|
const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create());
|
2017-10-17 18:30:33 -04:00
|
|
|
const atlasGlyphRect = calculatePixelRectForGlyph(glyphUnitMetrics,
|
|
|
|
glyphPixelOrigin,
|
2017-11-30 12:51:07 -05:00
|
|
|
pixelsPerUnit,
|
2017-10-17 18:30:33 -04:00
|
|
|
hint);
|
|
|
|
|
|
|
|
this.glyphSizes.push(glmatrix.vec2.clone([
|
|
|
|
glyphUnitMetrics.right - glyphUnitMetrics.left,
|
|
|
|
glyphUnitMetrics.ascent - glyphUnitMetrics.descent,
|
|
|
|
]));
|
|
|
|
|
|
|
|
const atlasGlyphBL = atlasGlyphRect.slice(0, 2) as glmatrix.vec2;
|
|
|
|
const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2;
|
|
|
|
glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE);
|
|
|
|
glmatrix.vec2.div(atlasGlyphTR, atlasGlyphTR, ATLAS_SIZE);
|
|
|
|
|
|
|
|
this.glyphTexCoords.push(glmatrix.vec4.clone([
|
|
|
|
atlasGlyphBL[0],
|
|
|
|
atlasGlyphBL[1],
|
|
|
|
atlasGlyphTR[0],
|
|
|
|
atlasGlyphTR[1],
|
|
|
|
]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ThreeDAtlasCameraView implements CameraView {
|
|
|
|
get width(): number {
|
|
|
|
return ATLAS_SIZE[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
get height(): number {
|
|
|
|
return ATLAS_SIZE[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
get classList(): DOMTokenList | null {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
addEventListener<K extends keyof HTMLElementEventMap>(type: K,
|
|
|
|
listener: (this: HTMLCanvasElement,
|
|
|
|
ev: HTMLElementEventMap[K]) =>
|
|
|
|
any,
|
|
|
|
useCapture?: boolean): void {}
|
|
|
|
|
|
|
|
getBoundingClientRect(): ClientRect {
|
|
|
|
return new ClientRect();
|
|
|
|
}
|
2017-08-31 20:08:22 -04:00
|
|
|
}
|
|
|
|
|
2017-08-31 19:11:09 -04:00
|
|
|
function main() {
|
|
|
|
const controller = new ThreeDController;
|
|
|
|
window.addEventListener('load', () => controller.start(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|