@itwin/core-frontend
Version:
iTwin.js frontend components
243 lines • 9.88 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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