// pathfinder/client/src/meshes.ts // // Copyright © 2017 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. import * as base64js from 'base64-js'; import * as _ from 'lodash'; import {expectNotNull, FLOAT32_SIZE, panic, PathfinderError, UINT16_SIZE} from './utils'; import {UINT32_MAX, UINT32_SIZE} from './utils'; interface BufferTypeFourCCTable { [fourCC: string]: keyof Meshes; } interface ArrayLike { [index: number]: T; } interface VertexExpansionDescriptor { expanded: T[]; original: ArrayLike; size: number; } interface VertexCopyResult { originalStartIndex: number; originalEndIndex: number; expandedStartIndex: number; expandedEndIndex: number; } type PrimitiveType = 'Uint16' | 'Uint32' | 'Float32'; type PrimitiveTypeArray = Float32Array | Uint16Array | Uint32Array; interface MeshBufferTypeDescriptor { type: PrimitiveType; size: number; } const PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS = { Float32: Float32Array, Uint16: Uint16Array, Uint32: Uint32Array, }; export const B_QUAD_SIZE: number = 4 * 8; export const B_QUAD_UPPER_LEFT_VERTEX_OFFSET: number = 4 * 0; export const B_QUAD_UPPER_RIGHT_VERTEX_OFFSET: number = 4 * 1; export const B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 2; export const B_QUAD_LOWER_LEFT_VERTEX_OFFSET: number = 4 * 4; export const B_QUAD_LOWER_RIGHT_VERTEX_OFFSET: number = 4 * 5; export const B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 6; export const B_QUAD_UPPER_INDICES_OFFSET: number = B_QUAD_UPPER_LEFT_VERTEX_OFFSET; export const B_QUAD_LOWER_INDICES_OFFSET: number = B_QUAD_LOWER_LEFT_VERTEX_OFFSET; const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE; const MESH_TYPES: Meshes = { bQuads: { type: 'Uint32', size: B_QUAD_FIELD_COUNT }, bVertexLoopBlinnData: { type: 'Uint32', size: 1 }, bVertexPathIDs: { type: 'Uint16', size: 1 }, bVertexPositions: { type: 'Float32', size: 2 }, coverCurveIndices: { type: 'Uint32', size: 1 }, coverInteriorIndices: { type: 'Uint32', size: 1 }, edgeBoundingBoxPathIDs: { type: 'Uint16', size: 1 }, edgeBoundingBoxVertexPositions: { type: 'Float32', size: 4 }, edgeLowerCurvePathIDs: { type: 'Uint16', size: 1 }, edgeLowerCurveVertexPositions: { type: 'Float32', size: 6 }, edgeLowerLinePathIDs: { type: 'Uint16', size: 1 }, edgeLowerLineVertexPositions: { type: 'Float32', size: 4 }, edgeUpperCurvePathIDs: { type: 'Uint16', size: 1 }, edgeUpperCurveVertexPositions: { type: 'Float32', size: 6 }, edgeUpperLinePathIDs: { type: 'Uint16', size: 1 }, edgeUpperLineVertexPositions: { type: 'Float32', size: 4 }, segmentCurveNormals: { type: 'Float32', size: 3 }, segmentCurvePathIDs: { type: 'Uint16', size: 1 }, segmentCurves: { type: 'Float32', size: 6 }, segmentLineNormals: { type: 'Float32', size: 2 }, segmentLinePathIDs: { type: 'Uint16', size: 1 }, segmentLines: { type: 'Float32', size: 4 }, }; const BUFFER_TYPES: Meshes = { bQuads: 'ARRAY_BUFFER', bVertexLoopBlinnData: 'ARRAY_BUFFER', bVertexPathIDs: 'ARRAY_BUFFER', bVertexPositions: 'ARRAY_BUFFER', coverCurveIndices: 'ELEMENT_ARRAY_BUFFER', coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER', edgeBoundingBoxPathIDs: 'ARRAY_BUFFER', edgeBoundingBoxVertexPositions: 'ARRAY_BUFFER', edgeLowerCurvePathIDs: 'ARRAY_BUFFER', edgeLowerCurveVertexPositions: 'ARRAY_BUFFER', edgeLowerLinePathIDs: 'ARRAY_BUFFER', edgeLowerLineVertexPositions: 'ARRAY_BUFFER', edgeUpperCurvePathIDs: 'ARRAY_BUFFER', edgeUpperCurveVertexPositions: 'ARRAY_BUFFER', edgeUpperLinePathIDs: 'ARRAY_BUFFER', edgeUpperLineVertexPositions: 'ARRAY_BUFFER', segmentCurveNormals: 'ARRAY_BUFFER', segmentCurvePathIDs: 'ARRAY_BUFFER', segmentCurves: 'ARRAY_BUFFER', segmentLineNormals: 'ARRAY_BUFFER', segmentLinePathIDs: 'ARRAY_BUFFER', segmentLines: 'ARRAY_BUFFER', }; const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve']; const RIFF_FOURCC: string = 'RIFF'; const MESH_LIBRARY_FOURCC: string = 'PFML'; // Must match the FourCCs in `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into()`. const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = { bqua: 'bQuads', bvlb: 'bVertexLoopBlinnData', bvpi: 'bVertexPathIDs', bvpo: 'bVertexPositions', cvci: 'coverCurveIndices', cvii: 'coverInteriorIndices', ebbp: 'edgeBoundingBoxPathIDs', ebbv: 'edgeBoundingBoxVertexPositions', elcp: 'edgeLowerCurvePathIDs', elcv: 'edgeLowerCurveVertexPositions', ellp: 'edgeLowerLinePathIDs', ellv: 'edgeLowerLineVertexPositions', eucp: 'edgeUpperCurvePathIDs', eucv: 'edgeUpperCurveVertexPositions', eulp: 'edgeUpperLinePathIDs', eulv: 'edgeUpperLineVertexPositions', scpi: 'segmentCurvePathIDs', scur: 'segmentCurves', slin: 'segmentLines', slpi: 'segmentLinePathIDs', sncu: 'segmentCurveNormals', snli: 'segmentLineNormals', }; type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER'; export interface Meshes { readonly bQuads: T; readonly bVertexPositions: T; readonly bVertexPathIDs: T; readonly bVertexLoopBlinnData: T; readonly coverInteriorIndices: T; readonly coverCurveIndices: T; readonly edgeBoundingBoxPathIDs: T; readonly edgeBoundingBoxVertexPositions: T; readonly edgeLowerCurvePathIDs: T; readonly edgeLowerCurveVertexPositions: T; readonly edgeLowerLinePathIDs: T; readonly edgeLowerLineVertexPositions: T; readonly edgeUpperCurvePathIDs: T; readonly edgeUpperCurveVertexPositions: T; readonly edgeUpperLinePathIDs: T; readonly edgeUpperLineVertexPositions: T; readonly segmentLines: T; readonly segmentCurves: T; readonly segmentLinePathIDs: T; readonly segmentCurvePathIDs: T; readonly segmentLineNormals: T; readonly segmentCurveNormals: T; } export class PathfinderMeshData implements Meshes { readonly bQuads: ArrayBuffer; readonly bVertexPositions: ArrayBuffer; readonly bVertexPathIDs: ArrayBuffer; readonly bVertexLoopBlinnData: ArrayBuffer; readonly coverInteriorIndices: ArrayBuffer; readonly coverCurveIndices: ArrayBuffer; readonly edgeBoundingBoxPathIDs: ArrayBuffer; readonly edgeBoundingBoxVertexPositions: ArrayBuffer; readonly edgeLowerCurvePathIDs: ArrayBuffer; readonly edgeLowerCurveVertexPositions: ArrayBuffer; readonly edgeLowerLinePathIDs: ArrayBuffer; readonly edgeLowerLineVertexPositions: ArrayBuffer; readonly edgeUpperCurvePathIDs: ArrayBuffer; readonly edgeUpperCurveVertexPositions: ArrayBuffer; readonly edgeUpperLinePathIDs: ArrayBuffer; readonly edgeUpperLineVertexPositions: ArrayBuffer; readonly segmentLines: ArrayBuffer; readonly segmentCurves: ArrayBuffer; readonly segmentLinePathIDs: ArrayBuffer; readonly segmentCurvePathIDs: ArrayBuffer; readonly segmentLineNormals: ArrayBuffer; readonly segmentCurveNormals: ArrayBuffer; readonly bQuadCount: number; readonly edgeLowerCurveCount: number; readonly edgeUpperCurveCount: number; readonly edgeLowerLineCount: number; readonly edgeUpperLineCount: number; readonly segmentLineCount: number; readonly segmentCurveCount: number; constructor(meshes: ArrayBuffer | Meshes) { if (meshes instanceof ArrayBuffer) { // RIFF encoded data. if (toFourCC(meshes, 0) !== RIFF_FOURCC) panic("Supplied array buffer is not a mesh library (no RIFF header)!"); if (toFourCC(meshes, 8) !== MESH_LIBRARY_FOURCC) panic("Supplied array buffer is not a mesh library (no PFML header)!"); let offset = 12; while (offset < meshes.byteLength) { const fourCC = toFourCC(meshes, offset); const chunkLength = (new Uint32Array(meshes.slice(offset + 4, offset + 8)))[0]; if (BUFFER_TYPE_FOURCCS.hasOwnProperty(fourCC)) { const startOffset = offset + 8; const endOffset = startOffset + chunkLength; this[BUFFER_TYPE_FOURCCS[fourCC]] = meshes.slice(startOffset, endOffset); } offset += chunkLength + 8; } } else { for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) this[bufferName] = meshes[bufferName]; } this.bQuadCount = this.bQuads.byteLength / B_QUAD_SIZE; this.edgeUpperLineCount = this.edgeUpperLinePathIDs.byteLength / 2; this.edgeLowerLineCount = this.edgeLowerLinePathIDs.byteLength / 2; this.edgeUpperCurveCount = this.edgeUpperCurvePathIDs.byteLength / 2; this.edgeLowerCurveCount = this.edgeLowerCurvePathIDs.byteLength / 2; this.segmentCurveCount = this.segmentCurvePathIDs.byteLength / 2; this.segmentLineCount = this.segmentLinePathIDs.byteLength / 2; } expand(pathIDs: number[]): PathfinderMeshData { const tempOriginalBuffers: any = {}, tempExpandedArrays: any = {}; for (const key of Object.keys(BUFFER_TYPES) as Array>) { const arrayConstructor = PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS[MESH_TYPES[key].type]; tempOriginalBuffers[key] = new arrayConstructor(this[key]); tempExpandedArrays[key] = []; } const originalBuffers: Meshes = tempOriginalBuffers; const expandedArrays: Meshes = tempExpandedArrays; for (let newPathIndex = 0; newPathIndex < pathIDs.length; newPathIndex++) { const expandedPathID = newPathIndex + 1; const originalPathID = pathIDs[newPathIndex]; const bVertexCopyResult = copyVertices(['bVertexPositions', 'bVertexLoopBlinnData'], 'bVertexPathIDs', expandedArrays, originalBuffers, expandedPathID, originalPathID); if (bVertexCopyResult == null) continue; const firstExpandedBVertexIndex = bVertexCopyResult.expandedStartIndex; const firstBVertexIndex = bVertexCopyResult.originalStartIndex; const lastBVertexIndex = bVertexCopyResult.originalEndIndex; // Copy over edge data. copyVertices(['edgeBoundingBoxVertexPositions'], 'edgeBoundingBoxPathIDs', expandedArrays, originalBuffers, expandedPathID, originalPathID); for (const edgeBufferName of EDGE_BUFFER_NAMES) { copyVertices([`edge${edgeBufferName}VertexPositions` as keyof Meshes], `edge${edgeBufferName}PathIDs` as keyof Meshes, expandedArrays, originalBuffers, expandedPathID, originalPathID); } // Copy over indices. copyIndices(expandedArrays.coverInteriorIndices, originalBuffers.coverInteriorIndices as Uint32Array, firstExpandedBVertexIndex, firstBVertexIndex, lastBVertexIndex); copyIndices(expandedArrays.coverCurveIndices, originalBuffers.coverCurveIndices as Uint32Array, firstExpandedBVertexIndex, firstBVertexIndex, lastBVertexIndex); // Copy over B-quads. let firstBQuadIndex = findFirstBQuadIndex(originalBuffers.bQuads as Uint32Array, originalBuffers.bVertexPathIDs as Uint16Array, originalPathID); if (firstBQuadIndex == null) firstBQuadIndex = originalBuffers.bQuads.length; const indexDelta = firstExpandedBVertexIndex - firstBVertexIndex; for (let bQuadIndex = firstBQuadIndex; bQuadIndex < originalBuffers.bQuads.length / B_QUAD_FIELD_COUNT; bQuadIndex++) { const bQuad = originalBuffers.bQuads[bQuadIndex]; if (originalBuffers.bVertexPathIDs[originalBuffers.bQuads[bQuadIndex * B_QUAD_FIELD_COUNT]] !== originalPathID) { break; } for (let indexIndex = 0; indexIndex < B_QUAD_FIELD_COUNT; indexIndex++) { const srcIndex = originalBuffers.bQuads[bQuadIndex * B_QUAD_FIELD_COUNT + indexIndex]; if (srcIndex === UINT32_MAX) expandedArrays.bQuads.push(srcIndex); else expandedArrays.bQuads.push(srcIndex + indexDelta); } } // Copy over segments. copySegments(['segmentLines', 'segmentLineNormals'], 'segmentLinePathIDs', expandedArrays, originalBuffers, expandedPathID, originalPathID); copySegments(['segmentCurves', 'segmentCurveNormals'], 'segmentCurvePathIDs', expandedArrays, originalBuffers, expandedPathID, originalPathID); } const tempExpandedBuffers: any = {}; for (const key of Object.keys(MESH_TYPES) as Array>) { const bufferType = MESH_TYPES[key].type; const arrayConstructor = PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS[bufferType]; const expandedBuffer = new ArrayBuffer(expandedArrays[key].length * sizeOfPrimitive(bufferType)); (new arrayConstructor(expandedBuffer)).set(expandedArrays[key]); tempExpandedBuffers[key] = expandedBuffer; } const expandedBuffers = tempExpandedBuffers as Meshes; return new PathfinderMeshData(expandedBuffers); } } export class PathfinderMeshBuffers implements Meshes { readonly bQuads: WebGLBuffer; readonly bVertexPositions: WebGLBuffer; readonly bVertexPathIDs: WebGLBuffer; readonly bVertexLoopBlinnData: WebGLBuffer; readonly coverInteriorIndices: WebGLBuffer; readonly coverCurveIndices: WebGLBuffer; readonly edgeBoundingBoxPathIDs: WebGLBuffer; readonly edgeBoundingBoxVertexPositions: WebGLBuffer; readonly edgeLowerCurvePathIDs: WebGLBuffer; readonly edgeLowerCurveVertexPositions: WebGLBuffer; readonly edgeLowerLinePathIDs: WebGLBuffer; readonly edgeLowerLineVertexPositions: WebGLBuffer; readonly edgeUpperCurvePathIDs: WebGLBuffer; readonly edgeUpperCurveVertexPositions: WebGLBuffer; readonly edgeUpperLinePathIDs: WebGLBuffer; readonly edgeUpperLineVertexPositions: WebGLBuffer; readonly segmentLines: WebGLBuffer; readonly segmentCurves: WebGLBuffer; readonly segmentLinePathIDs: WebGLBuffer; readonly segmentCurvePathIDs: WebGLBuffer; readonly segmentLineNormals: WebGLBuffer; readonly segmentCurveNormals: WebGLBuffer; constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) { for (const bufferName of Object.keys(BUFFER_TYPES) as Array) { const bufferType = gl[BUFFER_TYPES[bufferName]]; const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!"); gl.bindBuffer(bufferType, buffer); gl.bufferData(bufferType, meshData[bufferName], gl.STATIC_DRAW); this[bufferName] = buffer; } } } function copyVertices(vertexBufferNames: Array>, pathIDBufferName: keyof Meshes, expandedMeshes: Meshes, originalMeshes: Meshes, expandedPathID: number, originalPathID: number): VertexCopyResult | null { const expandedPathIDs = expandedMeshes[pathIDBufferName]; const originalPathIDs = originalMeshes[pathIDBufferName]; const firstOriginalVertexIndex = _.sortedIndex(originalPathIDs, originalPathID); if (firstOriginalVertexIndex < 0) return null; const firstExpandedVertexIndex = expandedPathIDs.length; let lastOriginalVertexIndex = firstOriginalVertexIndex; while (lastOriginalVertexIndex < originalPathIDs.length && originalPathIDs[lastOriginalVertexIndex] === originalPathID) { for (const vertexBufferName of vertexBufferNames) { const expanded = expandedMeshes[vertexBufferName]; const original = originalMeshes[vertexBufferName]; const size = MESH_TYPES[vertexBufferName].size; for (let elementIndex = 0; elementIndex < size; elementIndex++) { const globalIndex = size * lastOriginalVertexIndex + elementIndex; expanded.push(original[globalIndex]); } } expandedPathIDs.push(expandedPathID); lastOriginalVertexIndex++; } return { expandedEndIndex: expandedPathIDs.length, expandedStartIndex: firstExpandedVertexIndex, originalEndIndex: lastOriginalVertexIndex, originalStartIndex: firstOriginalVertexIndex, }; } function copyIndices(destIndices: number[], srcIndices: Uint32Array, firstExpandedIndex: number, firstIndex: number, lastIndex: number, validateIndex?: (indexIndex: number) => boolean) { if (firstIndex === lastIndex) return; // FIXME(pcwalton): Speed this up somehow. let indexIndex = srcIndices.findIndex(index => index >= firstIndex && index < lastIndex); if (indexIndex < 0) return; const indexDelta = firstExpandedIndex - firstIndex; while (indexIndex < srcIndices.length) { const index = srcIndices[indexIndex]; if (validateIndex == null || validateIndex(indexIndex)) { if (index < firstIndex || index >= lastIndex) break; destIndices.push(index + indexDelta); } else { destIndices.push(index); } indexIndex++; } } function copySegments(segmentBufferNames: Array>, pathIDBufferName: keyof Meshes, expandedMeshes: Meshes, originalMeshes: Meshes, expandedPathID: number, originalPathID: number): void { let segmentIndex = _.indexOf(originalMeshes[pathIDBufferName] as Uint16Array, originalPathID); while (segmentIndex < originalMeshes[pathIDBufferName].length) { if (originalMeshes[pathIDBufferName][segmentIndex] !== originalPathID) break; for (const segmentBufferName of segmentBufferNames) { if (originalMeshes[segmentBufferName].length === 0) continue; const size = MESH_TYPES[segmentBufferName].size; for (let fieldIndex = 0; fieldIndex < size; fieldIndex++) { const srcIndex = size * segmentIndex + fieldIndex; expandedMeshes[segmentBufferName].push(originalMeshes[segmentBufferName][srcIndex]); } } expandedMeshes[pathIDBufferName].push(expandedPathID); segmentIndex++; } } function sizeOfPrimitive(primitiveType: PrimitiveType): number { switch (primitiveType) { case 'Uint16': return UINT16_SIZE; case 'Uint32': return UINT32_SIZE; case 'Float32': return FLOAT32_SIZE; } } function findFirstBQuadIndex(bQuads: Uint32Array, bVertexPathIDs: Uint16Array, queryPathID: number): number | null { for (let bQuadIndex = 0; bQuadIndex < bQuads.length / B_QUAD_FIELD_COUNT; bQuadIndex++) { const thisPathID = bVertexPathIDs[bQuads[bQuadIndex * B_QUAD_FIELD_COUNT]]; if (thisPathID === queryPathID) return bQuadIndex; } return null; } function toFourCC(buffer: ArrayBuffer, position: number): string { let result = ""; const bytes = new Uint8Array(buffer, position, 4); for (const byte of bytes) result += String.fromCharCode(byte); return result; } export function parseServerTiming(headers: Headers): number { if (!headers.has('Server-Timing')) return 0.0; const timing = headers.get('Server-Timing')!; const matches = /^Partitioning\s*=\s*([0-9.]+)$/.exec(timing); return matches != null ? parseFloat(matches[1]) / 1000.0 : 0.0; }