@itwin/core-frontend
Version:
iTwin.js frontend components
514 lines • 25.8 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 { base64StringToUint8Array } from "@itwin/core-bentley";
import { ColorIndex, FeatureIndex, FeatureIndexType, FillFlags, Gradient, ImageBuffer, ImageSource, ImageSourceFormat, isValidImageSourceFormat, QParams3d, QPoint3dList, RenderTexture, TextureTransparency, } from "@itwin/core-common";
import { Point2d, Point3d, Range3d, Transform } from "@itwin/core-geometry";
import { IModelApp } from "../IModelApp";
import { createGraphicFromDescription, createGraphicTemplateFromDescription } from "../tile/internal";
import { ToolAdmin } from "../tools/ToolAdmin";
import { imageElementFromImageSource, tryImageElementFromUrl } from "../common/ImageUtil";
import { createPointStringParams } from "../common/internal/render/PointStringParams";
import { createPolylineParams } from "../common/internal/render/PolylineParams";
import { GraphicBranch } from "./GraphicBranch";
import { RenderGraphicOwner } from "./RenderGraphic";
import { createMeshParams } from "../common/internal/render/VertexTableBuilder";
import { _featureTable, _implementationProhibited, _textures, _transformCenter, _transforms } from "../common/internal/Symbols";
// cSpell:ignore deserializing subcat uninstanced wiremesh qorigin trimesh
/** Default implementation of RenderGraphicOwner. */
class GraphicOwner extends RenderGraphicOwner {
_graphic;
constructor(_graphic) {
super();
this._graphic = _graphic;
}
get graphic() { return this._graphic; }
}
/** Transparency settings for planar grid display.
* @alpha
*/
export class PlanarGridTransparency {
/** Transparency for the grid plane. This should generally be fairly high to avoid obscuring other geometry */
planeTransparency = .9;
/** Transparency of the grid lines. This should be higher than the plane, but less than reference line transparency */
lineTransparency = .75;
/** Transparency of the reference lines. This should be less than plane or line transparency so that reference lines are more prominent */
refTransparency = .5;
}
/** A RenderSystem provides access to resources used by the internal WebGL-based rendering system.
* An application rarely interacts directly with the RenderSystem; instead it interacts with types like [[Viewport]] which
* coordinate with the RenderSystem on the application's behalf.
* @see [Display system overview]($docs/learning/display/index.md)
* @see [[IModelApp.renderSystem]].
* @public
* @extensions
*/
export class RenderSystem {
/** Options used to initialize the RenderSystem. These are primarily used for feature-gating.
* This object is frozen and cannot be modified after the RenderSystem is created.
* @internal
*/
options;
/** Antialias samples to use on all subsequently created render targets.
* Default value: undefined (no antialiasing)
* @beta
*/
antialiasSamples;
/** Initialize the RenderSystem with the specified options.
* @note The RenderSystem takes ownership of the supplied Options and freezes it.
* @internal
*/
constructor(options) {
this.options = undefined !== options ? options : {};
Object.freeze(this.options);
if (undefined !== this.options.disabledExtensions)
Object.freeze(this.options.disabledExtensions);
}
[Symbol.dispose]() {
this.dispose(); // eslint-disable-line @typescript-eslint/no-deprecated
}
/** The maximum permitted width or height of a texture supported by this render system. */
get maxTextureSize() { return 0; }
/** @internal */
get supportsCreateImageBitmap() { return false; }
/** @internal */
get dpiAwareLOD() { return true === this.options.dpiAwareLOD; }
/** @internal */
get isMobile() { return false; }
/** Find a previously-created [RenderMaterial]($common) by its ID.
* @param _key The unique ID of the material within the context of the IModelConnection. Typically an element ID.
* @param _imodel The IModelConnection with which the material is associated.
* @returns A previously-created material matching the specified ID, or undefined if no such material exists.
*/
findMaterial(_key, _imodel) { return undefined; }
/** Create a [RenderMaterial]($common).
* @see [[CreateRenderMaterialArgs]] for a description of the material parameters.
*/
createRenderMaterial(_args) {
return undefined;
}
/** Creates a [[GraphicBuilder]] for creating a [[RenderGraphic]].
* @param placement The local-to-world transform in which the builder's geometry is to be defined.
* @param type The type of builder to create.
* @param viewport The viewport in which the resultant [[RenderGraphic]] will be rendered.
* @param pickableId If the decoration is to be pickable, a unique identifier to associate with the resultant [[RenderGraphic]].
* @returns A builder for creating a [[RenderGraphic]] of the specified type appropriate for rendering within the specified viewport.
* @see [[IModelConnection.transientIds]] for obtaining an ID for a pickable decoration.
* @see [[RenderContext.createGraphicBuilder]].
* @see [[Decorator]]
*/
createGraphicBuilder(placement, type, viewport, pickableId) {
const pickable = undefined !== pickableId ? { id: pickableId } : undefined;
return this.createGraphic({ type, viewport, placement, pickable });
}
/** Obtain an object capable of producing a custom screen-space effect to be applied to the image rendered by a [[Viewport]].
* @returns undefined if screen-space effects are not supported by this RenderSystem.
*/
createScreenSpaceEffectBuilder(_params) {
return undefined;
}
/** @internal */
createTriMesh(args, instances) {
const params = createMeshParams(args, this.maxTextureSize, IModelApp.tileAdmin.edgeOptions.type !== "non-indexed");
return this.createMesh(params, instances);
}
/** @internal */
createMeshGraphics(mesh, instances) {
const meshArgs = mesh.toMeshArgs();
if (meshArgs) {
return this.createTriMesh(meshArgs, instances);
}
const polylineArgs = mesh.toPolylineArgs();
return polylineArgs ? this.createIndexedPolylines(polylineArgs, instances) : undefined;
}
/** @internal */
createGeometryFromMesh(mesh, viOrigin, tileData) {
const meshArgs = mesh.toMeshArgs();
if (meshArgs) {
const meshParams = createMeshParams(meshArgs, this.maxTextureSize, IModelApp.tileAdmin.edgeOptions.type !== "non-indexed");
meshParams.tileData = tileData;
return this.createMeshGeometry(meshParams, viOrigin);
}
const plArgs = mesh.toPolylineArgs();
if (!plArgs) {
return undefined;
}
if (plArgs.flags.isDisjoint) {
const psParams = createPointStringParams(plArgs, this.maxTextureSize);
return psParams ? this.createPointStringGeometry(psParams, viOrigin) : undefined;
}
const plParams = createPolylineParams(plArgs, this.maxTextureSize);
return plParams ? this.createPolylineGeometry(plParams, viOrigin) : undefined;
}
/** @internal */
createIndexedPolylines(args, instances) {
if (args.flags.isDisjoint) {
const pointStringParams = createPointStringParams(args, this.maxTextureSize);
return undefined !== pointStringParams ? this.createPointString(pointStringParams, instances) : undefined;
}
else {
const polylineParams = createPolylineParams(args, this.maxTextureSize);
return undefined !== polylineParams ? this.createPolyline(polylineParams, instances) : undefined;
}
}
/** @internal */
createMeshGeometry(_params, _viewIndependentOrigin) { return undefined; }
/** @internal */
createPolylineGeometry(_params, _viewIndependentOrigin) { return undefined; }
/** @internal */
createPointStringGeometry(_params, _viewIndependentOrigin) { return undefined; }
/** @internal */
createPointCloudGeometry(_args) { return undefined; }
/** @internal */
createRealityMeshGeometry(_params, _disableTextureDisposal = false) { return undefined; }
/** @internal */
createAreaPattern(_params) { return undefined; }
/** Create a [[RenderInstances]] from a [[RenderInstancesParams]], to be supplied to [[createGraphicFromTemplate]] via [[CreateGraphicFromTempalateArgs.instances]].
* @beta
*/
createRenderInstances(_params) { return undefined; }
createGraphicFromGeometry(createGeometry, instancesOrOrigin) {
let viOrigin;
let instances;
if (instancesOrOrigin instanceof Point3d)
viOrigin = instancesOrOrigin;
else
instances = instancesOrOrigin;
const geom = createGeometry(viOrigin);
return geom ? this.createRenderGraphic(geom, instances) : undefined;
}
/** @internal */
createMesh(params, instances) {
return this.createGraphicFromGeometry((viOrigin) => this.createMeshGeometry(params, viOrigin), instances);
}
/** @internal */
createPolyline(params, instances) {
return this.createGraphicFromGeometry((origin) => this.createPolylineGeometry(params, origin), instances);
}
/** @internal */
createPointString(params, instances) {
return this.createGraphicFromGeometry((origin) => this.createPointStringGeometry(params, origin), instances);
}
/** @internal */
createTerrainMesh(_params, _transform, _disableTextureDisposal = false) {
return undefined;
}
/** @internal */
createRealityMeshGraphic(_params, _disableTextureDisposal = false) { return undefined; }
/** @internal */
createRealityMesh(realityMesh, disableTextureDisposal = false) {
const geom = this.createRealityMeshGeometry(realityMesh, disableTextureDisposal);
return geom ? this.createRenderGraphic(geom) : undefined;
}
/** @internal */
get maxRealityImageryLayers() { return 0; }
/** @internal */
createPointCloud(args, _imodel) {
const geom = this.createPointCloudGeometry(args);
return geom ? this.createRenderGraphic(geom) : undefined;
}
/** Create a clip volume to clip geometry.
* @note The clip volume takes ownership of the ClipVector, which must not be subsequently mutated.
* @param _clipVector Defines how the volume clips geometry.
* @returns A clip volume, or undefined if, e.g., the clip vector does not clip anything.
*/
createClipVolume(_clipVector) { return undefined; }
/** @internal */
createPlanarGrid(_frustum, _grid) { return undefined; }
/** @internal */
createBackgroundMapDrape(_drapedTree, _mapTree) { return undefined; }
/** @internal */
createTile(tileTexture, corners, featureIndex) {
// corners
// [0] [1]
// [2] [3]
// Quantize the points according to their range
const points = new QPoint3dList(QParams3d.fromRange(Range3d.create(...corners)));
for (let i = 0; i < 4; i++)
points.add(corners[i]);
// Now remove the translation from the quantized points and put it into a transform instead.
// This prevents graphical artifacts when quantization origin is large relative to quantization scale.
// ###TODO: Would be better not to create a branch for every tile.
const qorigin = points.params.origin;
const transform = Transform.createTranslationXYZ(qorigin.x, qorigin.y, qorigin.z);
qorigin.setZero();
const features = new FeatureIndex();
if (undefined !== featureIndex) {
features.featureID = featureIndex;
features.type = FeatureIndexType.Uniform;
}
const rasterTile = {
points,
vertIndices: [0, 1, 2, 2, 1, 3],
isPlanar: true,
features,
colors: new ColorIndex(),
fillFlags: FillFlags.None,
textureMapping: {
uvParams: [new Point2d(0, 0), new Point2d(1, 0), new Point2d(0, 1), new Point2d(1, 1)],
texture: tileTexture,
},
};
const trimesh = this.createTriMesh(rasterTile);
if (undefined === trimesh)
return undefined;
const branch = new GraphicBranch(true);
branch.add(trimesh);
return this.createBranch(branch, transform);
}
/** Create a Graphic for a [[SkyBox]] which encompasses the entire scene, rotating with the camera.
* @internal
*/
createSkyBox(_params) { return undefined; }
/** Create a RenderGraphic consisting of a list of Graphics, with optional transform and symbology overrides applied to the list */
createBranch(branch, transform, options) {
return this.createGraphicBranch(branch, transform, options);
}
/** Create a node in the scene graph corresponding to a transform node in the scene's schedule script.
* Nodes under this branch will only be drawn if they belong to the specified transform node.
* This allows the graphics in a single Tile to be efficiently drawn with different transforms applied by different nodes.
* The node Id is either the Id of a single transform node in the script, of 0xffffffff to indicate all nodes that have no transform applied to them.
* @internal
*/
createAnimationTransformNode(graphic, _nodeId) {
return graphic;
}
/** Return a Promise which when resolved indicates that all pending external textures have finished loading from the backend. */
async waitForAllExternalTextures() { return Promise.resolve(); }
/** @internal */
get hasExternalTextureRequests() { return false; }
/** Create a graphic that assumes ownership of another graphic.
* @param ownedGraphic The RenderGraphic to be owned.
* @returns The owning graphic that exposes a `disposeGraphic` method for explicitly disposing of the owned graphic.
* @see [[RenderGraphicOwner]] for details regarding ownership semantics.
* @public
*/
createGraphicOwner(ownedGraphic) { return new GraphicOwner(ownedGraphic); }
/** Create a "layer" containing the graphics belonging to it. A layer has a unique identifier and all of its geometry lies in an XY plane.
* Different layers can be drawn coincident with one another; their draw order can be controlled by a per-layer priority value so that one layer draws
* on top of another. Layers cannot nest inside other layers. Multiple GraphicLayers can exist with the same ID; they are treated as belonging to the same layer.
* A GraphicLayer must be contained (perhaps indirectly) inside a GraphicLayerContainer.
* @see [[createGraphicLayerContainer]]
* @internal
*/
createGraphicLayer(graphic, _layerId) { return graphic; }
/** Create a graphic that can contain [[GraphicLayer]]s.
* @internal
*/
createGraphicLayerContainer(graphic, _drawAsOverlay, _transparency, _elevation) { return graphic; }
/** Find a previously-created [[RenderTexture]] by its key.
* @param _key The unique key of the texture within the context of the IModelConnection. Typically an element Id.
* @param _imodel The IModelConnection with which the texture is associated.
* @returns A previously-created texture matching the specified key, or undefined if no such texture exists.
*/
findTexture(_key, _imodel) {
return undefined;
}
/** Find or create a [[RenderTexture]] from a persistent texture element.
* @param id The ID of the texture element.
* @param iModel The IModel containing the texture element.
* @returns A Promise resolving to the created RenderTexture or to undefined if the texture could not be created.
* @note If the texture is successfully created, it will be cached on the IModelConnection such that it can later be retrieved by its ID using [[RenderSystem.findTexture]].
* @see [[RenderSystem.loadTextureImage]].
*/
async loadTexture(id, iModel) {
let texture = this.findTexture(id.toString(), iModel);
if (undefined === texture) {
const image = await this.loadTextureImage(id, iModel);
if (undefined !== image) {
// This will return a pre-existing RenderTexture if somebody else loaded it while we were awaiting the image.
texture = this.createTexture({
type: RenderTexture.Type.Normal,
ownership: { key: id, iModel },
image: {
source: image.image,
transparency: ImageSourceFormat.Png === image.format ? TextureTransparency.Mixed : TextureTransparency.Opaque,
},
});
}
}
return texture;
}
/**
* Load a texture image given the ID of a texture element.
* @param id The ID of the texture element.
* @param iModel The IModel containing the texture element.
* @returns A Promise resolving to a TextureImage created from the texture element's data, or to undefined if the TextureImage could not be created.
* @see [[RenderSystem.loadTexture]]
* @internal
*/
async loadTextureImage(id, iModel) {
const elemProps = await iModel.elements.getProps(id);
if (1 !== elemProps.length)
return undefined;
const textureProps = elemProps[0];
if (undefined === textureProps.data || "string" !== typeof (textureProps.data) || undefined === textureProps.format || "number" !== typeof (textureProps.format))
return undefined;
const format = textureProps.format;
if (!isValidImageSourceFormat(format))
return undefined;
const imageSource = new ImageSource(base64StringToUint8Array(textureProps.data), format);
const image = await imageElementFromImageSource(imageSource);
return { image, format };
}
/** Obtain a texture created from a gradient.
* @param _symb The description of the gradient.
* @param _imodel The IModelConnection with which the texture is associated.
* @returns A texture created from the gradient image, or undefined if the texture could not be created.
* @note If a texture matching the specified gradient is already cached on the iModel, it will be returned.
* Otherwise, if an iModel is supplied, the newly-created texture will be cached on the iModel such that subsequent calls with an equivalent gradient and the
* same iModel will return the cached texture instead of creating a new one.
*/
getGradientTexture(_symb, _imodel) {
return undefined;
}
/** Create a texture from an ImageSource. */
async createTextureFromSource(args) {
try {
// JPEGs don't support transparency.
const transparency = ImageSourceFormat.Jpeg === args.source.format ? TextureTransparency.Opaque : (args.transparency ?? TextureTransparency.Mixed);
const image = await imageElementFromImageSource(args.source);
if (!IModelApp.hasRenderSystem)
return undefined;
return this.createTexture({
type: args.type,
ownership: args.ownership,
image: {
source: image,
transparency,
},
});
}
catch {
return undefined;
}
}
/** Create a new texture by its element ID. This texture will be retrieved asynchronously from the backend. A placeholder image will be associated with the texture until the requested image data loads. */
createTextureFromElement(_id, _imodel, _params, _format) {
return undefined;
}
createTexture(_args) {
return undefined;
}
/** Create a new texture from a cube of HTML images.
* @internal
*/
createTextureFromCubeImages(_posX, _negX, _posY, _negY, _posZ, _negZ, _imodel, _params) {
return undefined;
}
/** @internal */
onInitialized() { }
/** @internal */
get supportsLogZBuffer() { return false !== this.options.logarithmicDepthBuffer; }
/** Obtain an object that can be used to control various debugging features. Returns `undefined` if debugging features are unavailable for this `RenderSystem`.
* @internal
*/
get debugControl() { return undefined; }
/** @internal */
collectStatistics(_stats) { }
/** A function that is invoked after the WebGL context is lost. Context loss is almost always caused by excessive consumption of GPU memory.
* After context loss occurs, the RenderSystem will be unable to interact with WebGL by rendering viewports, creating graphics and textures, etc.
* By default, this function invokes [[ToolAdmin.exceptionHandler]] with a brief message describing what occurred.
* An application can override this behavior as follows:
* ```ts
* RenderSystem.contextLossHandler = (): Promise<any> => {
* // your implementation here.
* }
* ```
* @note Context loss is reported by the browser some short time *after* it has occurred. It is not possible to determine the specific cause.
* @see [[TileAdmin.gpuMemoryLimit]] to limit the amount of GPU memory consumed thereby reducing the likelihood of context loss.
* @see [[TileAdmin.totalTileContentBytes]] for the amount of GPU memory allocated for tile graphics.
*/
static async contextLossHandler() {
const msg = IModelApp.localization.getLocalizedString("iModelJs:Errors.WebGLContextLost");
return ToolAdmin.exceptionHandler(msg);
}
/** Convert a [[GraphicDescription]] produced by a [[GraphicDescriptionBuilder]] into a [[RenderGraphic]].
* @beta
*/
createGraphicFromDescription(args) {
return createGraphicFromDescription(args.description, args.context, this);
}
/** Convert a [[GraphicDescription]] produced by a [[GraphicDescriptionBuilder]] into a [[GraphicTemplate]].
* @beta
*/
createTemplateFromDescription(args) {
return createGraphicTemplateFromDescription(args.description, args.context, this);
}
/** Obtain the JSON representation of a [[WorkerGraphicDescriptionContext]] for the specified `iModel` that can be forwarded to a Worker for use with a [[GraphicDescriptionBuilder]].
* @beta
*/
createWorkerGraphicDescriptionContextProps(iModel) {
const props = {
[_implementationProhibited]: undefined,
transientIds: iModel.transientIds.fork(),
constraints: {
[_implementationProhibited]: undefined,
maxTextureSize: this.maxTextureSize,
},
};
return props;
}
/** Synchronize changes made to a [[WorkerGraphicDescriptionContext]] on a Worker with the state of the `iModel` from which it was created.
* @beta
*/
async resolveGraphicDescriptionContext(props, iModel) {
const impl = props;
if (typeof impl.transientIds !== "object" || !Array.isArray(impl.textures)) {
throw new Error("Invalid GraphicDescriptionContextProps");
}
if (impl.resolved) {
throw new Error("resolveGraphicDescriptionContext can only be called once for a given GraphicDescriptionContextProps");
}
const textures = new Map();
await Promise.allSettled(impl.textures.map(async (tex, i) => {
let texture;
if (tex.source.gradient) {
texture = this.getGradientTexture(Gradient.Symb.fromJSON(tex.source.gradient));
}
else if (tex.source.imageSource) {
texture = await this.createTextureFromSource({
source: new ImageSource(tex.source.imageSource, tex.source.format),
type: tex.type,
transparency: tex.transparency,
});
}
else if (tex.source.imageBuffer) {
texture = this.createTexture({
type: tex.type,
image: {
source: ImageBuffer.create(tex.source.imageBuffer, tex.source.format, tex.source.width),
transparency: tex.transparency,
},
});
}
else if (tex.source.url) {
const image = await tryImageElementFromUrl(tex.source.url);
if (image) {
texture = this.createTexture({
type: tex.type,
image: {
source: image,
transparency: tex.transparency,
},
});
}
}
if (texture) {
textures.set(i.toString(10), texture);
}
}));
const remap = iModel.transientIds.merge(impl.transientIds);
impl.resolved = true;
return {
[_implementationProhibited]: undefined,
remapTransientLocalId: (source) => remap(source),
[_textures]: textures,
};
}
}
//# sourceMappingURL=RenderSystem.js.map