@itwin/core-frontend
Version:
iTwin.js frontend components
337 lines • 16.3 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MeshBuilderPolyface = exports.MeshEdgeCreationOptions = exports.MeshBuilder = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const Primitives_1 = require("./Primitives");
const VertexKey_1 = require("./VertexKey");
const MeshPrimitives_1 = require("./MeshPrimitives");
/** @internal */
class MeshBuilder {
vertexMap;
_triangleSet;
_currentPolyface;
mesh;
tolerance;
areaTolerance;
tileRange;
get currentPolyface() { return this._currentPolyface; }
get displayParams() { return this.mesh.displayParams; }
set displayParams(params) { this.mesh.displayParams = params; }
/** create reference for triangleSet on demand */
get triangleSet() {
if (undefined === this._triangleSet)
this._triangleSet = new Primitives_1.TriangleSet();
return this._triangleSet;
}
constructor(mesh, tolerance, areaTolerance, tileRange) {
this.mesh = mesh;
this.tolerance = tolerance;
this.areaTolerance = areaTolerance;
this.tileRange = tileRange;
let vertexTolerance;
if (mesh.points instanceof core_common_1.QPoint3dList) {
const p0 = mesh.points.params.unquantize(0, 0, 0);
const p1 = mesh.points.params.unquantize(1, 1, 1);
vertexTolerance = p1.minus(p0, p0);
}
else {
vertexTolerance = { x: tolerance, y: tolerance, z: tolerance };
}
this.vertexMap = new VertexKey_1.VertexMap(vertexTolerance);
}
/** create a new MeshBuilder */
static create(props) {
const mesh = MeshPrimitives_1.Mesh.create(props);
const { tolerance, areaTolerance, range } = props;
return new MeshBuilder(mesh, tolerance, areaTolerance, range);
}
/**
* iterate through each point list of the strokes primitive and either load the point string or polyline into builder
* @param strokes lists of stroke primitive point lists to iterate
* @param isDisjoint if true add point string, else add polyline
* @param fillColor
*/
addStrokePointLists(strokes, isDisjoint, fillColor, feature) {
for (const strokePoints of strokes) {
if (isDisjoint)
this.addPointString(strokePoints.points, fillColor, feature);
else
this.addPolyline(strokePoints.points, fillColor, feature);
}
}
/**
* add data from polyface into mesh builder
* @param polyface the indexed polyface to iterate the facets of to load each facet's triangles' vertices
* @param props the properties required for this operation
*/
addFromPolyface(polyface, props, feature) {
this.beginPolyface(polyface, props.edgeOptions);
const visitor = polyface.createVisitor();
while (visitor.moveToNextFacet()) {
this.addFromPolyfaceVisitor(visitor, props, feature);
}
this.endPolyface();
}
/**
* @param visitor the PolyfaceVisitor containing the face data to be added
* @param props the properties required for this operation:
*/
addFromPolyfaceVisitor(visitor, options, feature) {
const { pointCount, normalCount, paramCount, requireNormals } = visitor;
const { includeParams, mappedTexture } = options;
const isDegenerate = requireNormals && normalCount < pointCount; // TFS#790263: Degenerate triangle - no normals.
// a triangle must have at least 3 points
if (pointCount < 3 || isDegenerate)
return;
const haveParam = includeParams && paramCount > 0;
const triangleCount = pointCount - 2;
(0, core_bentley_1.assert)(!includeParams || paramCount > 0);
(0, core_bentley_1.assert)(!haveParam || undefined !== mappedTexture);
// The face represented by this visitor should be convex (we request that in facet options) - so we do a simple fan triangulation.
const polyfaceVisitorOptions = { ...options, triangleCount, haveParam };
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) {
const triangle = this.createTriangle(triangleIndex, visitor, polyfaceVisitorOptions, feature);
if (undefined !== triangle)
this.addTriangle(triangle);
}
}
createTriangleVertices(triangleIndex, visitor, options, feature) {
const { point, requireNormals } = visitor;
const { fillColor, haveParam } = options;
// If we do not have UVParams stored on the IndexedPolyface, compute them now
let params;
if (haveParam && options.mappedTexture) {
(0, core_bentley_1.assert)(this.mesh.points.length === 0 || this.mesh.uvParams.length !== 0);
const mappedTexture = options.mappedTexture;
const transformToImodel = mappedTexture.params.textureMatrix.transform;
if (transformToImodel)
params = mappedTexture.computeUVParams(visitor, transformToImodel);
(0, core_bentley_1.assert)(params !== undefined);
}
const vertices = [];
for (let i = 0; i < 3; ++i) {
const vertexIndex = 0 === i ? 0 : triangleIndex + i;
const position = point.getPoint3dAtUncheckedPointIndex(vertexIndex);
const normal = requireNormals ? core_common_1.OctEncodedNormal.fromVector((0, core_bentley_1.expectDefined)(visitor.getNormal(vertexIndex))) : undefined;
const uvParam = params ? params[vertexIndex] : undefined;
vertices[i] = { position, fillColor, normal, uvParam, sourceIndex: vertexIndex, feature };
}
// Previously we would add all 3 vertices to our map, then detect degenerate triangles in AddTriangle().
// This led to unused vertex data, and caused mismatch in # of vertices when recreating the MeshBuilder from the data in the tile cache.
// Detect beforehand instead.
if (this.vertexMap.arePositionsAlmostEqual(vertices[0], vertices[1])
|| this.vertexMap.arePositionsAlmostEqual(vertices[0], vertices[2])
|| this.vertexMap.arePositionsAlmostEqual(vertices[1], vertices[2]))
return undefined;
return vertices;
}
createTriangle(triangleIndex, visitor, options, feature) {
// generate vertex key properties for each of the three sides of the triangle
const vertices = this.createTriangleVertices(triangleIndex, visitor, options, feature);
// avoid creating degenerate triangles
if (undefined === vertices)
return undefined;
const { edgeVisible } = visitor;
const triangle = new Primitives_1.Triangle();
triangle.setEdgeVisibility(0 === triangleIndex ? edgeVisible[0] : false, edgeVisible[triangleIndex + 1], triangleIndex === options.triangleCount - 1 ? edgeVisible[triangleIndex + 2] : false);
// set each triangle index to the index associated with the vertex key location in the vertex map
vertices.forEach((vertexProps, i) => {
let vertexKeyIndex;
if (visitor.auxData) {
// No deduplication with auxData (for now...)
vertexKeyIndex = this.mesh.addVertex(vertexProps);
this.mesh.addAuxChannels(visitor.auxData.channels, vertexProps.sourceIndex);
}
else {
vertexKeyIndex = this.addVertex(vertexProps);
}
triangle.indices[i] = vertexKeyIndex;
// if the current polyface exists, map the vertex key index to the visitor's client point index
if (this.currentPolyface !== undefined)
this.currentPolyface.vertexIndexMap.set(vertexKeyIndex, visitor.clientPointIndex(vertexProps.sourceIndex));
});
return triangle;
}
/** removed Feature for now */
addPolyline(points, fillColor, feature) {
const { mesh } = this;
const poly = new core_common_1.MeshPolyline();
for (const position of points)
poly.addIndex(this.addVertex({ position, fillColor, feature }));
mesh.addPolyline(poly);
}
/** removed Feature for now */
addPointString(points, fillColor, feature) {
const { mesh } = this;
const poly = new core_common_1.MeshPolyline();
for (const position of points)
poly.addIndex(this.addVertex({ position, fillColor, feature }));
mesh.addPolyline(poly);
}
beginPolyface(polyface, options) {
if (!options.generateNoEdges) {
const triangles = this.mesh.triangles;
this._currentPolyface = new MeshBuilderPolyface(polyface, options, triangles === undefined ? 0 : triangles.length);
}
}
endPolyface() {
const { currentPolyface, mesh } = this;
if (undefined === currentPolyface)
return;
this._currentPolyface = undefined;
buildMeshEdges(mesh, currentPolyface);
}
addVertex(vertex, addToMeshOnInsert = true) {
// if vertex key isn't duplicate, then also insert properties into mesh
const onInsert = (vk) => this.mesh.addVertex(vk);
return this.vertexMap.insertKey(vertex, addToMeshOnInsert ? onInsert : undefined);
}
addTriangle(triangle) {
// Attempt to avoid adding vertices originating from degenerate triangles before we get here.
// Removed assert and just return if degenerate at this point because uncommon cases (not worth testing for) can still occur.
if (triangle.isDegenerate)
return;
const onInsert = (_vk) => this.mesh.addTriangle(triangle);
this.triangleSet.insertKey(triangle, onInsert);
}
}
exports.MeshBuilder = MeshBuilder;
/** @internal */
class MeshEdgeCreationOptions {
type;
minCreaseAngle = 20.0 * core_geometry_1.Angle.radiansPerDegree;
get generateAllEdges() { return this.type === MeshEdgeCreationOptions.Type.AllEdges; }
get generateNoEdges() { return this.type === MeshEdgeCreationOptions.Type.NoEdges; }
get generateCreaseEdges() { return 0 !== (this.type & MeshEdgeCreationOptions.Type.CreaseEdges); }
/** Create edge chains for polyfaces that do not already have them. */
get createEdgeChains() { return 0 !== (this.type & MeshEdgeCreationOptions.Type.CreateChains); }
constructor(type = MeshEdgeCreationOptions.Type.NoEdges) { this.type = type; }
}
exports.MeshEdgeCreationOptions = MeshEdgeCreationOptions;
/** @internal */
(function (MeshEdgeCreationOptions) {
let Type;
(function (Type) {
Type[Type["NoEdges"] = 0] = "NoEdges";
Type[Type["CreaseEdges"] = 2] = "CreaseEdges";
Type[Type["SmoothEdges"] = 4] = "SmoothEdges";
Type[Type["CreateChains"] = 8] = "CreateChains";
Type[Type["DefaultEdges"] = 2] = "DefaultEdges";
Type[Type["AllEdges"] = 6] = "AllEdges";
})(Type = MeshEdgeCreationOptions.Type || (MeshEdgeCreationOptions.Type = {}));
})(MeshEdgeCreationOptions || (exports.MeshEdgeCreationOptions = MeshEdgeCreationOptions = {}));
/** @internal */
class MeshBuilderPolyface {
polyface;
edgeOptions;
vertexIndexMap = new Map();
baseTriangleIndex;
constructor(polyface, edgeOptions, baseTriangleIndex) {
this.polyface = polyface;
this.edgeOptions = edgeOptions;
this.baseTriangleIndex = baseTriangleIndex;
}
}
exports.MeshBuilderPolyface = MeshBuilderPolyface;
class EdgeInfo {
visible;
faceIndex0;
edge;
point0;
point1;
faceIndex1;
constructor(visible, faceIndex0, edge, point0, point1) {
this.visible = visible;
this.faceIndex0 = faceIndex0;
this.edge = edge;
this.point0 = point0;
this.point1 = point1;
}
addFace(visible, faceIndex) {
if (undefined === this.faceIndex1) {
this.visible ||= visible;
this.faceIndex1 = faceIndex;
}
}
}
function buildMeshEdges(mesh, polyface) {
if (!mesh.triangles)
return;
const edgeMap = new core_bentley_1.Dictionary((lhs, rhs) => lhs.compareTo(rhs));
const triangleNormals = [];
// We need to detect the edge pairs -- Can't do that from the Mesh indices as these are not shared - so we'll
// assume that the polyface indices are properly shared, this should be true as a seperate index array is used
// for Polyfaces.
const triangle = new Primitives_1.Triangle();
const polyfacePoints = [new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d()];
const polyfaceIndices = [0, 0, 0];
for (let triangleIndex = polyface.baseTriangleIndex; triangleIndex < mesh.triangles.length; triangleIndex++) {
let indexNotFound = false;
mesh.triangles.getTriangle(triangleIndex, triangle);
for (let j = 0; j < 3; j++) {
const foundPolyfaceIndex = polyface.vertexIndexMap.get(triangle.indices[j]);
(0, core_bentley_1.assert)(undefined !== foundPolyfaceIndex);
if (undefined === foundPolyfaceIndex) {
indexNotFound = true;
continue;
}
polyfaceIndices[j] = foundPolyfaceIndex;
polyface.polyface.data.getPoint(foundPolyfaceIndex, polyfacePoints[j]);
}
if (indexNotFound)
continue;
for (let j = 0; j < 3; j++) {
const jNext = (j + 1) % 3;
const triangleNormalIndex = triangleNormals.length;
const meshEdge = new core_common_1.MeshEdge(triangle.indices[j], triangle.indices[jNext]);
const polyfaceEdge = new core_common_1.MeshEdge(polyfaceIndices[j], polyfaceIndices[jNext]);
const edgeInfo = new EdgeInfo(triangle.isEdgeVisible(j), triangleNormalIndex, meshEdge, polyfacePoints[j], polyfacePoints[jNext]);
const findOrInsert = edgeMap.findOrInsert(polyfaceEdge, edgeInfo);
if (!findOrInsert.inserted)
findOrInsert.value.addFace(edgeInfo.visible, triangleNormalIndex);
}
const normal = core_geometry_1.Vector3d.createCrossProductToPoints(polyfacePoints[0], polyfacePoints[1], polyfacePoints[2]);
normal.normalizeInPlace();
triangleNormals.push(normal);
}
// If there is no visibility indication in the mesh, infer from the mesh geometry.
if (!polyface.edgeOptions.generateAllEdges) {
const minEdgeDot = Math.cos(polyface.edgeOptions.minCreaseAngle);
for (const edgeInfo of edgeMap.values()) {
if (undefined !== edgeInfo.faceIndex1) {
const normal0 = triangleNormals[edgeInfo.faceIndex0];
const normal1 = triangleNormals[edgeInfo.faceIndex1];
if (Math.abs(normal0.dotProduct(normal1)) > minEdgeDot)
edgeInfo.visible = false;
}
}
}
// Now populate the MeshEdges.
// ###TODO edge chains?
if (undefined === mesh.edges)
mesh.edges = new core_common_1.MeshEdges();
const maxPlanarDot = 0.999999;
for (const edgeInfo of edgeMap.values()) {
if (edgeInfo.visible) {
mesh.edges.visible.push(edgeInfo.edge);
}
else if (undefined !== edgeInfo.faceIndex1) {
const normal0 = triangleNormals[edgeInfo.faceIndex0];
const normal1 = triangleNormals[edgeInfo.faceIndex1];
if (Math.abs(normal0.dotProduct(normal1)) < maxPlanarDot) {
mesh.edges.silhouette.push(edgeInfo.edge);
mesh.edges.silhouetteNormals.push(new core_common_1.OctEncodedNormalPair(core_common_1.OctEncodedNormal.fromVector(normal0), core_common_1.OctEncodedNormal.fromVector(normal1)));
}
}
}
}
//# sourceMappingURL=MeshBuilder.js.map