@itwin/core-frontend
Version:
iTwin.js frontend components
408 lines • 20.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.GraphicAssembler = 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 Symbols_1 = require("../internal/Symbols");
const GraphicType_1 = require("./GraphicType");
const GeometryAccumulator_1 = require("../internal/render/GeometryAccumulator");
const DisplayParams_1 = require("../internal/render/DisplayParams");
;
/** Provides methods for assembling geometric primitives and symbology into a graphical representation.
* Two concrete implementations are provided:
* - [[GraphicBuilder]], for creating [[RenderGraphic]]s directly; and
* - [[GraphicDescriptionBuilder]], for creating a [[GraphicDescription]] from which a [[RenderGraphic]] can be produced.
*
* GraphicBuilder can only be used on the main JavaScript thread, so its use should be reserved for relatively simple, quick-to-produce graphics like [[Decorations]].
* GraphicDescriptionBuilder is designed for use in a [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker). It can be used to assemble more
* complex graphics without blocking the main thread.
*
* @note Most of the methods which add geometry to the builder take ownership of their inputs rather than cloning them.
* So, for example, if you pass an array of points to [[addLineString]], you should not subsequently modify that array.
* @public
* @extensions
*/
class GraphicAssembler {
/** @internal */
[Symbols_1._accumulator];
_graphicParams = new core_common_1.GraphicParams();
/** The local-to-world transform in which the builder's geometry is to be defined. */
placement;
/** The type of graphic to be produced. */
type;
/** If the graphic is to be interactive, specifies its Id and other options. */
pickable;
/** If true, the order in which geometry is added to the builder is preserved.
* This enables overlay and background graphics, which draw without using the depth buffer, to specify which geometry display in front of others when
* they overlap.
* For example, to draw an overlay containing a red shape with a white outline, you would add the shape to the GraphicAssember first, followed by its outline,
* to ensure the outline draws on top of the shape.
* This incurs a performance penalty due to the increased number of draw calls, and is never useful for graphics that draw using the depth buffer.
*/
preserveOrder;
/** If true, normal vectors will be generated for surfaces, allowing 3d geometry to receive lighting and reduce z-fighting. */
wantNormals;
/** If true, edges are generated for surfaces, to be drawn if edge display is enabled for the view in which the graphic is drawn. */
wantEdges;
/** @alpha */
analysisStyle;
/** @internal */
constructor(options) {
this.placement = options.placement?.clone() ?? core_geometry_1.Transform.createIdentity();
this.type = options.type;
this.pickable = options.pickable;
this.wantEdges = options.wantEdges;
this.preserveOrder = options.preserveOrder;
this.wantNormals = options.wantNormals;
this.analysisStyle = options.analysisStyle;
this[Symbols_1._accumulator] = new GeometryAccumulator_1.GeometryAccumulator({
analysisStyleDisplacement: this.analysisStyle?.displacement,
});
if (this.pickable) {
this.activateFeature(new core_common_1.Feature(this.pickable.id, this.pickable.subCategoryId, this.pickable.geometryClass));
}
}
/** Whether the builder's geometry is defined in [[CoordSystem.View]] coordinates.
* Only graphics of type [[GraphicType.ViewBackground]] or [[GraphicType.ViewOverlay]] are defined in view coordinates.
* @see [[isWorldCoordinates]].
*/
get isViewCoordinates() {
return this.type === GraphicType_1.GraphicType.ViewBackground || this.type === GraphicType_1.GraphicType.ViewOverlay;
}
/** Whether the builder's geometry is defined in [[CoordSystem.World]] coordinates - the inverse of [[isViewCoordinates]]. */
get isWorldCoordinates() {
return !this.isViewCoordinates;
}
/** True if the builder produces a graphic of [[GraphicType.Scene]]. */
get isSceneGraphic() {
return this.type === GraphicType_1.GraphicType.Scene;
}
/** True if the builder produces a graphic of [[GraphicType.ViewBackground]]. */
get isViewBackground() {
return this.type === GraphicType_1.GraphicType.ViewBackground;
}
/** True if the builder produces a graphic of [[GraphicType.WorldOverlay]] or [[GraphicType.ViewOerlay]]. */
get isOverlay() {
return this.type === GraphicType_1.GraphicType.ViewOverlay || this.type === GraphicType_1.GraphicType.WorldOverlay;
}
/** Sets the current active symbology for this builder. Any new geometry subsequently added to the builder will be drawn using the specified symbology.
* @param graphicParams The symbology to apply to subsequent geometry.
* @see [[setSymbology]] for a convenient way to set common symbology options.
*/
activateGraphicParams(graphicParams) {
graphicParams.clone(this._graphicParams);
}
/** Change the [Feature]($common) to be associated with subsequently-added geometry. This permits multiple features to be batched together into a single graphic
* for more efficient rendering.
* @note This method has no effect if [[pickable]] is not defined.
*/
activateFeature(feature) {
(0, core_bentley_1.assert)(undefined !== this.pickable, "GraphicAssembler.activateFeature has no effect if PickableGraphicOptions were not supplied");
if (this.pickable) {
this[Symbols_1._accumulator].currentFeature = feature;
}
}
/** Change the pickable Id to be associated with subsequently-added geometry. This permits multiple pickable objects to be batched together into a single graphic
* for more efficient rendering. This method calls [[activateFeature]], using the subcategory Id and [GeometryClass]($common) specified in [[pickable]]
* at construction, if any.
* @note This method has no effect if [[pickable]] is not defined.
*/
activatePickableId(id) {
const pick = this.pickable;
this.activateFeature(new core_common_1.Feature(id, pick?.subCategoryId, pick?.geometryClass));
}
/**
* Appends a 3d line string to the builder.
* @param points Array of vertices in the line string.
*/
addLineString(points) {
if (2 === points.length && points[0].isAlmostEqual(points[1]))
this[Symbols_1._accumulator].addPointString(points, this.getLinearDisplayParams(), this.placement);
else
this[Symbols_1._accumulator].addLineString(points, this.getLinearDisplayParams(), this.placement);
}
/**
* Appends a 2d line string to the builder.
* @param points Array of vertices in the line string.
* @param zDepth Z value in local coordinates to use for each point.
*/
addLineString2d(points, zDepth) {
const pts3d = copy2dTo3d(points, zDepth);
this.addLineString(pts3d);
}
/**
* Appends a 3d point string to the builder. The points are drawn disconnected, with a diameter in pixels defined by the builder's active [[GraphicParams.rasterWidth]].
* @param points Array of vertices in the point string.
*/
addPointString(points) {
this[Symbols_1._accumulator].addPointString(points, this.getLinearDisplayParams(), this.placement);
}
/**
* Appends a 2d point string to the builder. The points are drawn disconnected, with a diameter in pixels defined by the builder's active [[GraphicParams.rasterWidth]].
* @param points Array of vertices in the point string.
* @param zDepth Z value in local coordinates to use for each point.
*/
addPointString2d(points, zDepth) {
const pts3d = copy2dTo3d(points, zDepth);
this.addPointString(pts3d);
}
/**
* Appends a closed 3d planar region to the builder.
* @param points Array of vertices of the shape.
*/
addShape(points) {
const loop = core_geometry_1.Loop.create(core_geometry_1.LineString3d.create(points));
this[Symbols_1._accumulator].addLoop(loop, this.getMeshDisplayParams(), this.placement, false);
}
/**
* Appends a closed 2d region to the builder.
* @param points Array of vertices of the shape.
* @param zDepth Z value in local coordinates to use for each point.
*/
addShape2d(points, zDepth) {
const pts3d = copy2dTo3d(points, zDepth);
this.addShape(pts3d);
}
/**
* Appends a 3d open arc or closed ellipse to the builder.
* @param arc Description of the arc or ellipse.
* @param isEllipse If true, and if the arc defines a full sweep, then draw as a closed ellipse instead of an arc.
* @param filled If true, and isEllipse is also true, then draw ellipse filled.
*/
addArc(ellipse, isEllipse, filled) {
let curve;
if (isEllipse || filled) {
curve = core_geometry_1.Loop.create(ellipse);
}
else {
curve = core_geometry_1.Path.create(ellipse);
}
if (filled && !isEllipse && !ellipse.sweep.isFullCircle) {
const gapSegment = core_geometry_1.LineSegment3d.create(ellipse.startPoint(), ellipse.endPoint());
curve.children.push(gapSegment);
}
const displayParams = curve.isAnyRegionType ? this.getMeshDisplayParams() : this.getLinearDisplayParams();
if (curve instanceof core_geometry_1.Loop)
this[Symbols_1._accumulator].addLoop(curve, displayParams, this.placement, false);
else
this[Symbols_1._accumulator].addPath(curve, displayParams, this.placement, false);
}
/**
* Appends a 2d open arc or closed ellipse to the builder.
* @param arc Description of the arc or ellipse.
* @param isEllipse If true, and if the arc defines a full sweep, then draw as a closed ellipse instead of an arc.
* @param filled If true, and isEllipse is also true, then draw ellipse filled.
* @param zDepth Z value in local coordinates to use for each point in the arc or ellipse.
*/
addArc2d(ellipse, isEllipse, filled, zDepth) {
if (0.0 === zDepth) {
this.addArc(ellipse, isEllipse, filled);
}
else {
const ell = ellipse;
ell.center.z = zDepth;
this.addArc(ell, isEllipse, filled);
}
}
/** Append a 3d open path to the builder. */
addPath(path) {
this[Symbols_1._accumulator].addPath(path, this.getLinearDisplayParams(), this.placement, false);
}
/** Append a 3d planar region to the builder. */
addLoop(loop) {
this[Symbols_1._accumulator].addLoop(loop, this.getMeshDisplayParams(), this.placement, false);
}
/** Append a [CurvePrimitive]($core-geometry) to the builder. */
addCurvePrimitive(curve) {
switch (curve.curvePrimitiveType) {
case "lineString":
this.addLineString(curve.points);
break;
case "lineSegment":
this.addLineString([curve.startPoint(), curve.endPoint()]);
break;
case "arc":
this.addArc(curve, false, false);
break;
default:
const path = new core_geometry_1.Path();
if (path.tryAddChild(curve))
this.addPath(path);
break;
}
}
/** Append a mesh to the builder.
* @param meshData Describes the mesh
* @param _filled If the mesh describes a planar region, indicates whether its interior area should be drawn with fill in [[RenderMode.Wireframe]].
*/
addPolyface(meshData, _filled = false) {
this[Symbols_1._accumulator].addPolyface(meshData, this.getMeshDisplayParams(), this.placement);
}
/** Append a solid primitive to the builder. */
addSolidPrimitive(primitive) {
this[Symbols_1._accumulator].addSolidPrimitive(primitive, this.getMeshDisplayParams(), this.placement);
}
/** Append any primitive to the builder.
* @param primitive The graphic primitive to append.
*/
addPrimitive(primitive) {
switch (primitive.type) {
case "linestring":
this.addLineString(primitive.points);
break;
case "linestring2d":
this.addLineString2d(primitive.points, primitive.zDepth);
break;
case "pointstring":
this.addPointString(primitive.points);
break;
case "pointstring2d":
this.addPointString2d(primitive.points, primitive.zDepth);
break;
case "shape":
this.addShape(primitive.points);
break;
case "shape2d":
this.addShape2d(primitive.points, primitive.zDepth);
break;
case "arc":
this.addArc(primitive.arc, true === primitive.isEllipse, true === primitive.filled);
break;
case "arc2d":
this.addArc2d(primitive.arc, true === primitive.isEllipse, true === primitive.filled, primitive.zDepth);
break;
case "path":
this.addPath(primitive.path);
break;
case "loop":
this.addLoop(primitive.loop);
break;
case "polyface":
this.addPolyface(primitive.polyface, true === primitive.filled);
break;
case "solidPrimitive":
this.addSolidPrimitive(primitive.solidPrimitive);
break;
}
}
/** Add a box representing a volume of space. Typically used for debugging purposes.
* @param range The volume of space.
* @param solid If true, a [[Box]] solid primitive will be added; otherwise, a wireframe outline of the box will be added.
*/
addRangeBox(range, solid = false) {
if (!solid) {
this.addFrustum(core_common_1.Frustum.fromRange(range));
return;
}
const box = core_geometry_1.Box.createRange(range, true);
if (box)
this.addSolidPrimitive(box);
}
/** Add Frustum edges. Useful for debugging. */
addFrustum(frustum) {
this.addRangeBoxFromCorners(frustum.points);
}
/** Add Frustum sides. Useful for debugging. */
addFrustumSides(frustum) {
this.addRangeBoxSidesFromCorners(frustum.points);
}
/** Add range edges from corner points */
addRangeBoxFromCorners(p) {
this.addLineString([
p[core_common_1.Npc.LeftBottomFront],
p[core_common_1.Npc.LeftTopFront],
p[core_common_1.Npc.RightTopFront],
p[core_common_1.Npc.RightBottomFront],
p[core_common_1.Npc.RightBottomRear],
p[core_common_1.Npc.RightTopRear],
p[core_common_1.Npc.LeftTopRear],
p[core_common_1.Npc.LeftBottomRear],
p[core_common_1.Npc.LeftBottomFront].clone(),
p[core_common_1.Npc.RightBottomFront].clone(),
]);
this.addLineString([p[core_common_1.Npc.LeftTopFront].clone(), p[core_common_1.Npc.LeftTopRear].clone()]);
this.addLineString([p[core_common_1.Npc.RightTopFront].clone(), p[core_common_1.Npc.RightTopRear].clone()]);
this.addLineString([p[core_common_1.Npc.LeftBottomRear].clone(), p[core_common_1.Npc.RightBottomRear].clone()]);
}
/** Add range sides from corner points */
addRangeBoxSidesFromCorners(p) {
this.addShape([
p[core_common_1.Npc.LeftBottomFront].clone(),
p[core_common_1.Npc.LeftTopFront].clone(),
p[core_common_1.Npc.RightTopFront].clone(),
p[core_common_1.Npc.RightBottomFront].clone(),
p[core_common_1.Npc.LeftBottomFront].clone()
]);
this.addShape([
p[core_common_1.Npc.RightTopRear].clone(),
p[core_common_1.Npc.LeftTopRear].clone(),
p[core_common_1.Npc.LeftBottomRear].clone(),
p[core_common_1.Npc.RightBottomRear].clone(),
p[core_common_1.Npc.RightTopRear].clone()
]);
this.addShape([
p[core_common_1.Npc.RightTopRear].clone(),
p[core_common_1.Npc.LeftTopRear].clone(),
p[core_common_1.Npc.LeftTopFront].clone(),
p[core_common_1.Npc.RightTopFront].clone(),
p[core_common_1.Npc.RightTopRear].clone()
]);
this.addShape([
p[core_common_1.Npc.RightTopRear].clone(),
p[core_common_1.Npc.RightBottomRear].clone(),
p[core_common_1.Npc.RightBottomFront].clone(),
p[core_common_1.Npc.RightTopFront].clone(),
p[core_common_1.Npc.RightTopRear].clone()
]);
this.addShape([
p[core_common_1.Npc.LeftBottomRear].clone(),
p[core_common_1.Npc.RightBottomRear].clone(),
p[core_common_1.Npc.RightBottomFront].clone(),
p[core_common_1.Npc.LeftBottomFront].clone(),
p[core_common_1.Npc.LeftBottomRear].clone()
]);
this.addShape([
p[core_common_1.Npc.LeftBottomRear].clone(),
p[core_common_1.Npc.LeftTopRear].clone(),
p[core_common_1.Npc.LeftTopFront].clone(),
p[core_common_1.Npc.LeftBottomFront].clone(),
p[core_common_1.Npc.LeftBottomRear].clone()
]);
}
/** Sets the current active symbology for this builder. Any new geometry subsequently added will be drawn using the specified symbology.
* @param lineColor The color in which to draw lines.
* @param fillColor The color in which to draw filled regions.
* @param lineWidth The width in pixels to draw lines. The renderer will clamp this value to an integer in the range [1, 32].
* @param linePixels The pixel pattern in which to draw lines.
* @see [[activateGraphicParams]] for additional symbology options.
*/
setSymbology(lineColor, fillColor, lineWidth, linePixels = core_common_1.LinePixels.Solid) {
this.activateGraphicParams(core_common_1.GraphicParams.fromSymbology(lineColor, fillColor, lineWidth, linePixels));
}
/** Set the current active symbology for this builder to be a blanking fill before adding a planar region.
* A planar region drawn with blanking fill renders behind other geometry in the same graphic.
* Blanking fill is not affected by the fill [[ViewFlags]] being disabled.
* An example would be to add a line to a graphic containing a shape with blanking fill so that the line is always shown in front of the fill.
* @param fillColor The color in which to draw filled regions.
*/
setBlankingFill(fillColor) { this.activateGraphicParams(core_common_1.GraphicParams.fromBlankingFill(fillColor)); }
getMeshDisplayParams() { return DisplayParams_1.DisplayParams.createForMesh(this._graphicParams, !this.wantNormals, (grad) => this.resolveGradient(grad)); }
getLinearDisplayParams() { return DisplayParams_1.DisplayParams.createForLinear(this._graphicParams); }
add(geom) { this[Symbols_1._accumulator].addGeometry(geom); }
}
exports.GraphicAssembler = GraphicAssembler;
function copy2dTo3d(pts2d, depth) {
const pts3d = [];
for (const point of pts2d)
pts3d.push(core_geometry_1.Point3d.create(point.x, point.y, depth));
return pts3d;
}
//# sourceMappingURL=GraphicAssembler.js.map