UNPKG

@itwin/core-frontend

Version:
243 lines • 9.88 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Rendering */ import { VertexIndices } from "./VertexIndices"; import { tesselatePolylineList, wantJointTriangles } from "./PolylineParams"; import { assert } from "@itwin/core-bentley"; /** @internal */ export function calculateEdgeTableParams(numSegmentEdges, numSilhouettes, maxSize) { // Each segment edge requires 2 24-bit indices = 6 bytes = 1.5 RGBA values. // Each silhouette requires the same as segment edge plus 2 16-bit oct-encoded normals = 10 bytes = 2.5 RGBA values. let nRgbaRequired = Math.ceil(1.5 * numSegmentEdges + 2.5 * numSilhouettes); const silhouetteStartByteIndex = numSegmentEdges * 6; let silhouettePadding = 0; let width = nRgbaRequired; let height = 1; if (nRgbaRequired >= maxSize) { // Make roughly square to reduce unused space in last row. width = Math.ceil(Math.sqrt(nRgbaRequired)); // Each entry's data must fit on the same row. 15 RGBA = 60 bytes = lowest common multiple of 6, 10, and 4. const remainder = width % 15; if (0 !== remainder) width += 15 - remainder; // If the table contains both segments and silhouettes, there may be one row containing a mix of the two where padding // is required between them. if (numSilhouettes > 0 && numSegmentEdges > 0) { const silOffset = silhouetteStartByteIndex % 60; // some multiple of 6. silhouettePadding = (60 - silOffset) % 10; nRgbaRequired += Math.ceil(silhouettePadding / 4); } height = Math.ceil(nRgbaRequired / width); if (width * height < nRgbaRequired) height++; } return { width, height, silhouettePadding, silhouetteStartByteIndex, }; } function convertPolylinesAndEdges(polylines, edges) { let numIndices = undefined !== edges ? edges.length : 0; if (undefined !== polylines) for (const pd of polylines) numIndices += (pd.length - 1); if (0 === numIndices) return undefined; numIndices *= 6; const indexBytes = new Uint8Array(numIndices * 3); const endPointAndQuadIndexBytes = new Uint8Array(numIndices * 4); let ndx = 0; let ndx2 = 0; const addPoint = (p0, p1, quadIndex) => { VertexIndices.encodeIndex(p0, indexBytes, ndx); ndx += 3; VertexIndices.encodeIndex(p1, endPointAndQuadIndexBytes, ndx2); endPointAndQuadIndexBytes[ndx2 + 3] = quadIndex; ndx2 += 4; }; if (undefined !== polylines) { for (const pd of polylines) { const num = pd.length - 1; for (let i = 0; i < num; ++i) { let p0 = pd[i]; let p1 = pd[i + 1]; if (p1 < p0) { // swap so that lower index is first. p0 = p1; p1 = pd[i]; } addPoint(p0, p1, 0); addPoint(p1, p0, 2); addPoint(p0, p1, 1); addPoint(p0, p1, 1); addPoint(p1, p0, 2); addPoint(p1, p0, 3); } } } if (undefined !== edges) { for (const meshEdge of edges) { const p0 = meshEdge.indices[0]; const p1 = meshEdge.indices[1]; addPoint(p0, p1, 0); addPoint(p1, p0, 2); addPoint(p0, p1, 1); addPoint(p0, p1, 1); addPoint(p1, p0, 2); addPoint(p1, p0, 3); } } return { indices: new VertexIndices(indexBytes), endPointAndQuadIndices: endPointAndQuadIndexBytes, }; } function convertSilhouettes(edges, normalPairs) { const base = convertPolylinesAndEdges(undefined, edges); if (undefined === base) return undefined; const normalPairBytes = new Uint8Array(normalPairs.length * 6 * 4); const normalPair16 = new Uint16Array(normalPairBytes.buffer); let ndx = 0; for (const pair of normalPairs) { for (let i = 0; i < 6; i++) { normalPair16[ndx++] = pair.first.value; normalPair16[ndx++] = pair.second.value; } } return { indices: base.indices, endPointAndQuadIndices: base.endPointAndQuadIndices, normalPairs: normalPairBytes, }; } function buildIndexedEdges(args, polylines, maxSize) { const hardEdges = args.edges?.edges; const silhouettes = args.silhouettes; const numHardEdges = hardEdges?.length ?? 0; const numSilhouettes = silhouettes?.edges?.length ?? 0; const numPolylines = polylines ? polylines.reduce((count, pd) => count + Math.max(0, pd.length - 1), 0) : 0; const numSegmentEdges = numHardEdges + numPolylines; const numTotalEdges = numSegmentEdges + numSilhouettes; if (numTotalEdges === 0) return undefined; // Each edge is a quad consisting of six vertices. Each vertex is an identical 24-bit index into the lookup table. const indices = new VertexIndices(new Uint8Array(numTotalEdges * 6 * 3)); for (let i = 0; i < numTotalEdges; i++) for (let j = 0; j < 6; j++) indices.setNthIndex(i * 6 + j, i); const { width, height, silhouettePadding, silhouetteStartByteIndex } = calculateEdgeTableParams(numSegmentEdges, numSilhouettes, maxSize); const data = new Uint8Array(width * height * 4); function setUint24(byteIndex, value) { data[byteIndex + 0] = value & 0x0000ff; data[byteIndex + 1] = (value & 0x00ff00) >>> 8; data[byteIndex + 2] = (value & 0xff0000) >>> 16; } function setEdge(index, startPointIndex, endPointIndex) { const byteIndex = index * 6; setUint24(byteIndex, startPointIndex); setUint24(byteIndex + 3, endPointIndex); } let curIndex = 0; if (hardEdges) for (const edge of hardEdges) setEdge(curIndex++, edge.indices[0], edge.indices[1]); if (polylines) { for (const pd of polylines) { const num = pd.length - 1; for (let i = 0; i < num; i++) { const p0 = pd[i]; const p1 = pd[i + 1]; // Ensure lower index is first. if (p0 < p1) setEdge(curIndex++, p0, p1); else setEdge(curIndex++, p1, p0); } } } if (silhouettes?.edges) { assert(undefined !== silhouettes.normals); assert(silhouettes.normals.length === silhouettes.edges.length); function setSilhouette(index, start, end, normals) { const byteIndex = silhouetteStartByteIndex + silhouettePadding + index * 10; setUint24(byteIndex, start); setUint24(byteIndex + 3, end); data[byteIndex + 6] = normals.first.value & 0xff; data[byteIndex + 7] = (normals.first.value & 0xff00) >>> 8; data[byteIndex + 8] = normals.second.value & 0xff; data[byteIndex + 9] = (normals.second.value & 0xff00) >>> 8; } curIndex = 0; for (let i = 0; i < silhouettes.edges.length; i++) setSilhouette(curIndex++, silhouettes.edges[i].indices[0], silhouettes.edges[i].indices[1], silhouettes.normals[i]); } return { indices, edges: { data, width, height, numSegments: numSegmentEdges, silhouettePadding, }, appearance: { width: args.width, linePixels: args.linePixels, color: args.color, } }; } /** @internal */ export function createEdgeParams(args) { const { meshArgs, maxWidth, createIndexed } = args; const edgeArgs = meshArgs.edges; if (!edgeArgs) return undefined; // If we've got a single polyline edge group and we don't need to round its corners, convert it to segment edges. const polylinesToProcess = 1 === edgeArgs.polylines.groups?.length && undefined === edgeArgs.polylines.groups[0].appearance && !wantJointTriangles(edgeArgs.width, true === meshArgs.is2d) ? edgeArgs.polylines.groups[0].polylines.map((x) => x.indices) : undefined; let segments; let silhouettes; let indexed; if (createIndexed) { indexed = buildIndexedEdges(edgeArgs, polylinesToProcess, maxWidth); } else { segments = convertPolylinesAndEdges(polylinesToProcess, edgeArgs.edges.edges); silhouettes = edgeArgs.silhouettes.edges && edgeArgs.silhouettes.normals ? convertSilhouettes(edgeArgs.silhouettes.edges, edgeArgs.silhouettes.normals) : undefined; } let polylineGroups; if (!polylinesToProcess && edgeArgs.polylines.groups) { for (const group of edgeArgs.polylines.groups) { const polyline = tesselatePolylineList({ points: args.meshArgs.points, polylines: group.polylines, width: group.appearance?.width ?? edgeArgs.width, is2d: true === args.meshArgs.is2d, }); if (!polylineGroups) { polylineGroups = []; } polylineGroups.push({ polyline, appearance: group.appearance }); } } if (!segments && !silhouettes && !polylineGroups && !indexed) return undefined; return { weight: edgeArgs.width, linePixels: edgeArgs.linePixels, segments, silhouettes, polylineGroups, indexed, }; } //# sourceMappingURL=EdgeParams.js.map