From c2d026df4efeef20227b225f25208dc040aa9332 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Sat, 8 Jan 2022 13:54:47 -0500 Subject: [PATCH 01/10] GraphicBuilder supports view-independent origin. --- core/frontend/src/ViewContext.ts | 904 +++++++++--------- core/frontend/src/render/GraphicBuilder.ts | 5 + .../geometry/GeometryAccumulator.ts | 5 +- .../geometry/GeometryListBuilder.ts | 1 + .../render/primitives/mesh/MeshPrimitives.ts | 8 +- .../src/frontend/DecorationGeometryExample.ts | 13 +- 6 files changed, 476 insertions(+), 460 deletions(-) diff --git a/core/frontend/src/ViewContext.ts b/core/frontend/src/ViewContext.ts index 6faa3f1dfd68..444c93765ce6 100644 --- a/core/frontend/src/ViewContext.ts +++ b/core/frontend/src/ViewContext.ts @@ -1,462 +1,462 @@ -/*--------------------------------------------------------------------------------------------- -* 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 - */ - +/*--------------------------------------------------------------------------------------------- +* 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 { assert, Id64String } from "@itwin/core-bentley"; -import { - Matrix3d, Point2d, - Point3d, Range1d, Transform, XAndY, +import { + Matrix3d, Point2d, + Point3d, Range1d, Transform, XAndY, } from "@itwin/core-geometry"; import { Frustum, FrustumPlanes, SpatialClassifier, ViewFlags } from "@itwin/core-common"; -import { CachedDecoration, DecorationsCache } from "./DecorationsCache"; -import { IModelApp } from "./IModelApp"; -import { PlanarClipMaskState } from "./PlanarClipMaskState"; -import { CanvasDecoration } from "./render/CanvasDecoration"; -import { Decorations } from "./render/Decorations"; -import { GraphicBranch, GraphicBranchOptions } from "./render/GraphicBranch"; +import { CachedDecoration, DecorationsCache } from "./DecorationsCache"; +import { IModelApp } from "./IModelApp"; +import { PlanarClipMaskState } from "./PlanarClipMaskState"; +import { CanvasDecoration } from "./render/CanvasDecoration"; +import { Decorations } from "./render/Decorations"; +import { GraphicBranch, GraphicBranchOptions } from "./render/GraphicBranch"; import { GraphicBuilder, GraphicType, ViewportGraphicBuilderOptions } from "./render/GraphicBuilder"; -import { GraphicList, RenderGraphic } from "./render/RenderGraphic"; -import { RenderPlanarClassifier } from "./render/RenderPlanarClassifier"; -import { RenderSystem, RenderTextureDrape } from "./render/RenderSystem"; -import { RenderTarget } from "./render/RenderTarget"; -import { Scene } from "./render/Scene"; -import { SpatialClassifierTileTreeReference, Tile, TileGraphicType, TileLoadStatus, TileTreeReference } from "./tile/internal"; -import { ViewingSpace } from "./ViewingSpace"; -import { ELEMENT_MARKED_FOR_REMOVAL, ScreenViewport, Viewport, ViewportDecorator } from "./Viewport"; - -/** Provides context for producing [[RenderGraphic]]s for drawing within a [[Viewport]]. - * @public - */ -export class RenderContext { - /** ViewFlags extracted from the context's [[Viewport]]. */ - public readonly viewFlags: ViewFlags; - private readonly _viewport: Viewport; - /** Frustum extracted from the context's [[Viewport]]. */ - public readonly frustum: Frustum; - /** Frustum planes extracted from the context's [[Viewport]]. */ - public readonly frustumPlanes: FrustumPlanes; - - constructor(vp: Viewport, frustum?: Frustum) { - this._viewport = vp; +import { GraphicList, RenderGraphic } from "./render/RenderGraphic"; +import { RenderPlanarClassifier } from "./render/RenderPlanarClassifier"; +import { RenderSystem, RenderTextureDrape } from "./render/RenderSystem"; +import { RenderTarget } from "./render/RenderTarget"; +import { Scene } from "./render/Scene"; +import { SpatialClassifierTileTreeReference, Tile, TileGraphicType, TileLoadStatus, TileTreeReference } from "./tile/internal"; +import { ViewingSpace } from "./ViewingSpace"; +import { ELEMENT_MARKED_FOR_REMOVAL, ScreenViewport, Viewport, ViewportDecorator } from "./Viewport"; + +/** Provides context for producing [[RenderGraphic]]s for drawing within a [[Viewport]]. + * @public + */ +export class RenderContext { + /** ViewFlags extracted from the context's [[Viewport]]. */ + public readonly viewFlags: ViewFlags; + private readonly _viewport: Viewport; + /** Frustum extracted from the context's [[Viewport]]. */ + public readonly frustum: Frustum; + /** Frustum planes extracted from the context's [[Viewport]]. */ + public readonly frustumPlanes: FrustumPlanes; + + constructor(vp: Viewport, frustum?: Frustum) { + this._viewport = vp; this.viewFlags = vp.viewFlags; - this.frustum = frustum ? frustum : vp.getFrustum(); - this.frustumPlanes = new FrustumPlanes(this.frustum); - } - - /** Given a point in world coordinates, determine approximately how many pixels it occupies on screen based on this context's frustum. */ - public getPixelSizeAtPoint(inPoint?: Point3d): number { - return this.viewport.viewingSpace.getPixelSizeAtPoint(inPoint); - } - - /** The [[Viewport]] associated with this context. */ - public get viewport(): Viewport { - return this._viewport; - } - - /** The [[RenderSystem]] being used to produce graphics for this context. */ - public get renderSystem(): RenderSystem { - return this.target.renderSystem; - } - - /** @internal */ - public get target(): RenderTarget { return this.viewport.target; } - - /** @internal */ + this.frustum = frustum ? frustum : vp.getFrustum(); + this.frustumPlanes = new FrustumPlanes(this.frustum); + } + + /** Given a point in world coordinates, determine approximately how many pixels it occupies on screen based on this context's frustum. */ + public getPixelSizeAtPoint(inPoint?: Point3d): number { + return this.viewport.viewingSpace.getPixelSizeAtPoint(inPoint); + } + + /** The [[Viewport]] associated with this context. */ + public get viewport(): Viewport { + return this._viewport; + } + + /** The [[RenderSystem]] being used to produce graphics for this context. */ + public get renderSystem(): RenderSystem { + return this.target.renderSystem; + } + + /** @internal */ + public get target(): RenderTarget { return this.viewport.target; } + + /** @internal */ protected _createGraphicBuilder(options: Omit): GraphicBuilder { - return this.target.createGraphicBuilder({ ...options, viewport: this.viewport }); - } - - /** Create a builder for creating a [[GraphicType.Scene]] [[RenderGraphic]] for rendering within this context's [[Viewport]]. - * @param transform the local-to-world transform in which the builder's geometry is to be defined. - * @returns A builder for creating a [[GraphicType.Scene]] [[RenderGraphic]] for rendering within this context's [[Viewport]]. - */ - public createSceneGraphicBuilder(transform?: Transform): GraphicBuilder { - return this._createGraphicBuilder({ type: GraphicType.Scene, placement: transform }); - } - - /** Create a graphic from a [[GraphicBranch]]. */ - public createGraphicBranch(branch: GraphicBranch, location: Transform, opts?: GraphicBranchOptions): RenderGraphic { - return this.target.renderSystem.createGraphicBranch(branch, location, opts); - } - - /** Create a [[RenderGraphic]] which groups a set of graphics into a node in a scene graph, applying to each a transform and optional clip volume and symbology overrides. - * @param branch Contains the group of graphics and the symbology overrides. - * @param location the local-to-world transform applied to the grouped graphics. - * @returns A RenderGraphic suitable for drawing the scene graph node within this context's [[Viewport]]. - * @see [[RenderSystem.createBranch]] - */ - public createBranch(branch: GraphicBranch, location: Transform): RenderGraphic { return this.createGraphicBranch(branch, location); } - - /** Given the size of a logical pixel in meters, convert it to the size of a physical pixel in meters, if [[RenderSystem.dpiAwareLOD]] is `true`. - * Used when computing LOD for graphics. - * @internal - */ - public adjustPixelSizeForLOD(cssPixelSize: number): number { - return this.viewport.target.adjustPixelSizeForLOD(cssPixelSize); - } -} - -/** Provides context for an [[InteractiveTool]] to display decorations representing its current state. - * @see [[InteractiveTool.onDynamicFrame]] - * @public - */ -export class DynamicsContext extends RenderContext { - private _dynamics?: GraphicList; - - /** Add a graphic to the list of dynamic graphics to be drawn in this context's [[Viewport]]. */ - public addGraphic(graphic: RenderGraphic) { - if (undefined === this._dynamics) - this._dynamics = []; - this._dynamics.push(graphic); - } - - /** @internal */ - public changeDynamics(): void { - this.viewport.changeDynamics(this._dynamics); - } - - /** Create a builder for producing a [[RenderGraphic]] appropriate for rendering within this context's [[Viewport]]. - * @param options Options describing how to create the builder. - * @returns A builder that produces a [[RenderGraphic]]. - */ + return this.target.createGraphicBuilder({ ...options, viewport: this.viewport }); + } + + /** Create a builder for creating a [[GraphicType.Scene]] [[RenderGraphic]] for rendering within this context's [[Viewport]]. + * @param transform the local-to-world transform in which the builder's geometry is to be defined. + * @returns A builder for creating a [[GraphicType.Scene]] [[RenderGraphic]] for rendering within this context's [[Viewport]]. + */ + public createSceneGraphicBuilder(transform?: Transform): GraphicBuilder { + return this._createGraphicBuilder({ type: GraphicType.Scene, placement: transform }); + } + + /** Create a graphic from a [[GraphicBranch]]. */ + public createGraphicBranch(branch: GraphicBranch, location: Transform, opts?: GraphicBranchOptions): RenderGraphic { + return this.target.renderSystem.createGraphicBranch(branch, location, opts); + } + + /** Create a [[RenderGraphic]] which groups a set of graphics into a node in a scene graph, applying to each a transform and optional clip volume and symbology overrides. + * @param branch Contains the group of graphics and the symbology overrides. + * @param location the local-to-world transform applied to the grouped graphics. + * @returns A RenderGraphic suitable for drawing the scene graph node within this context's [[Viewport]]. + * @see [[RenderSystem.createBranch]] + */ + public createBranch(branch: GraphicBranch, location: Transform): RenderGraphic { return this.createGraphicBranch(branch, location); } + + /** Given the size of a logical pixel in meters, convert it to the size of a physical pixel in meters, if [[RenderSystem.dpiAwareLOD]] is `true`. + * Used when computing LOD for graphics. + * @internal + */ + public adjustPixelSizeForLOD(cssPixelSize: number): number { + return this.viewport.target.adjustPixelSizeForLOD(cssPixelSize); + } +} + +/** Provides context for an [[InteractiveTool]] to display decorations representing its current state. + * @see [[InteractiveTool.onDynamicFrame]] + * @public + */ +export class DynamicsContext extends RenderContext { + private _dynamics?: GraphicList; + + /** Add a graphic to the list of dynamic graphics to be drawn in this context's [[Viewport]]. */ + public addGraphic(graphic: RenderGraphic) { + if (undefined === this._dynamics) + this._dynamics = []; + this._dynamics.push(graphic); + } + + /** @internal */ + public changeDynamics(): void { + this.viewport.changeDynamics(this._dynamics); + } + + /** Create a builder for producing a [[RenderGraphic]] appropriate for rendering within this context's [[Viewport]]. + * @param options Options describing how to create the builder. + * @returns A builder that produces a [[RenderGraphic]]. + */ public createGraphic(options: Omit): GraphicBuilder { - return this._createGraphicBuilder(options); - } -} - -/** Provides context for a [[ViewportDecorator]] to add [[Decorations]] to be rendered within a [[Viewport]]. - * @public - */ -export class DecorateContext extends RenderContext { - private readonly _decorations: Decorations; - private readonly _cache: DecorationsCache; - private _curCacheableDecorator?: ViewportDecorator; - - /** The [[ScreenViewport]] in which this context's [[Decorations]] will be drawn. */ - public override get viewport(): ScreenViewport { - return super.viewport as ScreenViewport; - } - - /** @internal */ - constructor(vp: ScreenViewport, decorations: Decorations, cache: DecorationsCache) { - super(vp); - this._decorations = decorations; - this._cache = cache; - } - - /** Create a builder for creating a [[RenderGraphic]] of the specified type appropriate for rendering within this context's [[Viewport]]. - * @param type The type of builder to create. - * @param transform the local-to-world transform in which the builder's geometry is to be defined. - * @param id 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 this context's [[Viewport]]. - * @see [[IModelConnection.transientIds]] for obtaining an ID for a pickable decoration. - * @see [[createGraphic]] for more options. - */ - public createGraphicBuilder(type: GraphicType, transform?: Transform, id?: Id64String): GraphicBuilder { - return this.createGraphic({ type, placement: transform, pickable: undefined !== id ? { id } : undefined }); - } - - /** Create a builder for producing a [[RenderGraphic]] appropriate for rendering within this context's [[Viewport]]. - * @param options Options describing how to create the builder. - * @returns A builder that produces a [[RenderGraphic]]. - */ + return this._createGraphicBuilder(options); + } +} + +/** Provides context for a [[ViewportDecorator]] to add [[Decorations]] to be rendered within a [[Viewport]]. + * @public + */ +export class DecorateContext extends RenderContext { + private readonly _decorations: Decorations; + private readonly _cache: DecorationsCache; + private _curCacheableDecorator?: ViewportDecorator; + + /** The [[ScreenViewport]] in which this context's [[Decorations]] will be drawn. */ + public override get viewport(): ScreenViewport { + return super.viewport as ScreenViewport; + } + + /** @internal */ + constructor(vp: ScreenViewport, decorations: Decorations, cache: DecorationsCache) { + super(vp); + this._decorations = decorations; + this._cache = cache; + } + + /** Create a builder for creating a [[RenderGraphic]] of the specified type appropriate for rendering within this context's [[Viewport]]. + * @param type The type of builder to create. + * @param transform the local-to-world transform in which the builder's geometry is to be defined. + * @param id 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 this context's [[Viewport]]. + * @see [[IModelConnection.transientIds]] for obtaining an ID for a pickable decoration. + * @see [[createGraphic]] for more options. + */ + public createGraphicBuilder(type: GraphicType, transform?: Transform, id?: Id64String): GraphicBuilder { + return this.createGraphic({ type, placement: transform, pickable: undefined !== id ? { id } : undefined }); + } + + /** Create a builder for producing a [[RenderGraphic]] appropriate for rendering within this context's [[Viewport]]. + * @param options Options describing how to create the builder. + * @returns A builder that produces a [[RenderGraphic]]. + */ public createGraphic(options: Omit): GraphicBuilder { - return this._createGraphicBuilder(options); - } - - /** @internal */ - public addFromDecorator(decorator: ViewportDecorator): void { - assert(undefined === this._curCacheableDecorator); - try { - if (decorator.useCachedDecorations) { - const cached = this._cache.get(decorator); - if (cached) { - this.restoreCache(cached); - return; - } - - this._curCacheableDecorator = decorator; - } - - decorator.decorate(this); - } finally { - this._curCacheableDecorator = undefined; - } - } - - /** Restores decorations onto this context from the specified array of cached decorations. */ - private restoreCache(cachedDecorations: CachedDecoration[]) { - cachedDecorations.forEach((cachedDecoration) => { - switch (cachedDecoration.type) { - case "graphic": - this.addDecoration(cachedDecoration.graphicType, cachedDecoration.graphicOwner); - break; - case "canvas": - this.addCanvasDecoration(cachedDecoration.canvasDecoration, cachedDecoration.atFront); - break; - case "html": - this.addHtmlDecoration(cachedDecoration.htmlElement); - break; - } - }); - } - - private _appendToCache(decoration: CachedDecoration) { - assert(undefined !== this._curCacheableDecorator); - this._cache.add(this._curCacheableDecorator, decoration); - } - - /** Calls [[GraphicBuilder.finish]] on the supplied builder to obtain a [[RenderGraphic]], then adds the graphic to the appropriate list of - * [[Decorations]]. - * @param builder The builder from which to extract the graphic. - * @note The builder should not be used after calling this method. - */ - public addDecorationFromBuilder(builder: GraphicBuilder) { - this.addDecoration(builder.type, builder.finish()); - } - - /** Adds a graphic to the set of [[Decorations]] to be drawn in this context's [[ScreenViewport]]. - * @param The type of the graphic, which determines to which list of decorations it is added. - * @param decoration The decoration graphic to add. - * @note The type must match the type with which the [[RenderGraphic]]'s [[GraphicBuilder]] was constructed. - * @see [[DecorateContext.addDecorationFromBuilder]] for a more convenient API. - */ - public addDecoration(type: GraphicType, decoration: RenderGraphic) { - if (this._curCacheableDecorator) { - const graphicOwner = this.target.renderSystem.createGraphicOwner(decoration); - this._appendToCache({ type: "graphic", graphicOwner, graphicType: type }); - decoration = graphicOwner; - } - - switch (type) { - case GraphicType.Scene: - if (undefined === this._decorations.normal) - this._decorations.normal = []; - this._decorations.normal.push(decoration); - break; - - case GraphicType.WorldDecoration: - if (!this._decorations.world) - this._decorations.world = []; - this._decorations.world.push(decoration); - break; - - case GraphicType.WorldOverlay: - if (!this._decorations.worldOverlay) - this._decorations.worldOverlay = []; - this._decorations.worldOverlay.push(decoration); - break; - - case GraphicType.ViewOverlay: - if (!this._decorations.viewOverlay) - this._decorations.viewOverlay = []; - this._decorations.viewOverlay.push(decoration); - break; - - case GraphicType.ViewBackground: - this.setViewBackground(decoration); - break; - } - } - - /** Add a [[CanvasDecoration]] to be drawn in this context's [[ScreenViewport]]. */ - public addCanvasDecoration(decoration: CanvasDecoration, atFront = false) { - if (this._curCacheableDecorator) - this._appendToCache({ type: "canvas", canvasDecoration: decoration, atFront }); - - if (undefined === this._decorations.canvasDecorations) - this._decorations.canvasDecorations = []; - - const list = this._decorations.canvasDecorations; - if (0 === list.length || true === atFront) - list.push(decoration); - else - list.unshift(decoration); - } - - /** Add an HTMLElement to be drawn as a decoration in this context's [[ScreenViewport]]. */ - public addHtmlDecoration(decoration: HTMLElement) { - if (this._curCacheableDecorator) - this._appendToCache({ type: "html", htmlElement: decoration }); - - // an element decoration being added might already be on the decorationDiv, just marked for removal - if (decoration[ELEMENT_MARKED_FOR_REMOVAL]) { - decoration[ELEMENT_MARKED_FOR_REMOVAL] = false; + return this._createGraphicBuilder(options); + } + + /** @internal */ + public addFromDecorator(decorator: ViewportDecorator): void { + assert(undefined === this._curCacheableDecorator); + try { + if (decorator.useCachedDecorations) { + const cached = this._cache.get(decorator); + if (cached) { + this.restoreCache(cached); + return; + } + + this._curCacheableDecorator = decorator; + } + + decorator.decorate(this); + } finally { + this._curCacheableDecorator = undefined; + } + } + + /** Restores decorations onto this context from the specified array of cached decorations. */ + private restoreCache(cachedDecorations: CachedDecoration[]) { + cachedDecorations.forEach((cachedDecoration) => { + switch (cachedDecoration.type) { + case "graphic": + this.addDecoration(cachedDecoration.graphicType, cachedDecoration.graphicOwner); + break; + case "canvas": + this.addCanvasDecoration(cachedDecoration.canvasDecoration, cachedDecoration.atFront); + break; + case "html": + this.addHtmlDecoration(cachedDecoration.htmlElement); + break; + } + }); + } + + private _appendToCache(decoration: CachedDecoration) { + assert(undefined !== this._curCacheableDecorator); + this._cache.add(this._curCacheableDecorator, decoration); + } + + /** Calls [[GraphicBuilder.finish]] on the supplied builder to obtain a [[RenderGraphic]], then adds the graphic to the appropriate list of + * [[Decorations]]. + * @param builder The builder from which to extract the graphic. + * @note The builder should not be used after calling this method. + */ + public addDecorationFromBuilder(builder: GraphicBuilder) { + this.addDecoration(builder.type, builder.finish()); + } + + /** Adds a graphic to the set of [[Decorations]] to be drawn in this context's [[ScreenViewport]]. + * @param The type of the graphic, which determines to which list of decorations it is added. + * @param decoration The decoration graphic to add. + * @note The type must match the type with which the [[RenderGraphic]]'s [[GraphicBuilder]] was constructed. + * @see [[DecorateContext.addDecorationFromBuilder]] for a more convenient API. + */ + public addDecoration(type: GraphicType, decoration: RenderGraphic) { + if (this._curCacheableDecorator) { + const graphicOwner = this.target.renderSystem.createGraphicOwner(decoration); + this._appendToCache({ type: "graphic", graphicOwner, graphicType: type }); + decoration = graphicOwner; + } + + switch (type) { + case GraphicType.Scene: + if (undefined === this._decorations.normal) + this._decorations.normal = []; + this._decorations.normal.push(decoration); + break; + + case GraphicType.WorldDecoration: + if (!this._decorations.world) + this._decorations.world = []; + this._decorations.world.push(decoration); + break; + + case GraphicType.WorldOverlay: + if (!this._decorations.worldOverlay) + this._decorations.worldOverlay = []; + this._decorations.worldOverlay.push(decoration); + break; + + case GraphicType.ViewOverlay: + if (!this._decorations.viewOverlay) + this._decorations.viewOverlay = []; + this._decorations.viewOverlay.push(decoration); + break; + + case GraphicType.ViewBackground: + this.setViewBackground(decoration); + break; + } + } + + /** Add a [[CanvasDecoration]] to be drawn in this context's [[ScreenViewport]]. */ + public addCanvasDecoration(decoration: CanvasDecoration, atFront = false) { + if (this._curCacheableDecorator) + this._appendToCache({ type: "canvas", canvasDecoration: decoration, atFront }); + + if (undefined === this._decorations.canvasDecorations) + this._decorations.canvasDecorations = []; + + const list = this._decorations.canvasDecorations; + if (0 === list.length || true === atFront) + list.push(decoration); + else + list.unshift(decoration); + } + + /** Add an HTMLElement to be drawn as a decoration in this context's [[ScreenViewport]]. */ + public addHtmlDecoration(decoration: HTMLElement) { + if (this._curCacheableDecorator) + this._appendToCache({ type: "html", htmlElement: decoration }); + + // an element decoration being added might already be on the decorationDiv, just marked for removal + if (decoration[ELEMENT_MARKED_FOR_REMOVAL]) { + decoration[ELEMENT_MARKED_FOR_REMOVAL] = false; } else if (decoration.parentElement !== this.viewport.decorationDiv) { this.viewport.decorationDiv.appendChild(decoration); - } - } - - /** @internal */ - public drawStandardGrid(gridOrigin: Point3d, rMatrix: Matrix3d, spacing: XAndY, gridsPerRef: number, _isoGrid: boolean = false, _fixedRepetitions?: Point2d): void { - const vp = this.viewport; - - if (vp.viewingGlobe) - return; - - const color = vp.getContrastToBackgroundColor(); - const planarGrid = this.viewport.target.renderSystem.createPlanarGrid(vp.getFrustum(), { origin: gridOrigin, rMatrix, spacing, gridsPerRef, color }); - if (planarGrid) { - this.addDecoration(GraphicType.WorldDecoration, planarGrid); - } - } - - /** Display skyBox graphic that encompasses entire scene and rotates with camera. - * @see [[RenderSystem.createSkyBox]]. - */ - public setSkyBox(graphic: RenderGraphic) { - this._decorations.skyBox = graphic; - } - - /** Set the graphic to be displayed behind all other geometry as the background of this context's [[ScreenViewport]]. */ - public setViewBackground(graphic: RenderGraphic) { - this._decorations.viewBackground = graphic; - } -} - -/** Context used to create the scene to be drawn in a [[Viewport]]. The scene consists of a set of [[RenderGraphic]]s produced by the - * [[TileTree]]s visible within the viewport. Creating the scene may result in the enqueueing of requests for [[Tile]] content which - * should be displayed in the viewport but are not yet loaded. - * @public - */ -export class SceneContext extends RenderContext { - private _missingChildTiles = false; - /** The graphics comprising the scene. */ - public readonly scene = new Scene(); - - /** @internal */ - public readonly missingTiles = new Set(); - - /** @internal */ - public markChildrenLoading(): void { - this._missingChildTiles = true; - } - - /** @internal */ - public get hasMissingTiles(): boolean { - return this._missingChildTiles || this.missingTiles.size > 0; - } - - private _viewingSpace?: ViewingSpace; - private _graphicType: TileGraphicType = TileGraphicType.Scene; - - public constructor(vp: Viewport, frustum?: Frustum) { - super(vp, frustum); - } - - /** The viewed volume containing the scene. */ - public get viewingSpace(): ViewingSpace { - return undefined !== this._viewingSpace ? this._viewingSpace : this.viewport.viewingSpace; - } - - /** @internal */ - public get graphicType() { return this._graphicType; } - - /** Add the specified graphic to the scene. */ - public outputGraphic(graphic: RenderGraphic): void { - switch (this._graphicType) { - case TileGraphicType.BackgroundMap: - this.backgroundGraphics.push(graphic); - break; - case TileGraphicType.Overlay: - this.overlayGraphics.push(graphic); - break; - default: - this.graphics.push(graphic); - break; - } - } - - /** Indicate that the specified tile is desired for the scene but is not yet ready. A request to load its contents will later be enqueued. */ - public insertMissingTile(tile: Tile): void { - switch (tile.loadStatus) { - case TileLoadStatus.NotLoaded: - case TileLoadStatus.Queued: - case TileLoadStatus.Loading: - this.missingTiles.add(tile); - break; - } - } - - /** @internal */ - public requestMissingTiles(): void { - IModelApp.tileAdmin.requestTiles(this.viewport, this.missingTiles); - } - - /** @internal */ - public addPlanarClassifier(classifiedModelId: Id64String, classifierTree?: SpatialClassifierTileTreeReference, planarClipMask?: PlanarClipMaskState): RenderPlanarClassifier | undefined { - // Target may have the classifier from a previous frame; if not we must create one. - let classifier = this.viewport.target.getPlanarClassifier(classifiedModelId); - if (undefined === classifier) - classifier = this.viewport.target.createPlanarClassifier(classifierTree?.activeClassifier); - - // Either way, we need to collect the graphics to draw for this frame, and record that we did so. - if (undefined !== classifier) { - this.planarClassifiers.set(classifiedModelId, classifier); - classifier.setSource(classifierTree, planarClipMask); - } - - return classifier; - } - - /** @internal */ - public getPlanarClassifierForModel(modelId: Id64String) { - return this.planarClassifiers.get(modelId); - } - - /** @internal */ - public addBackgroundDrapedModel(drapedTreeRef: TileTreeReference, _heightRange: Range1d | undefined): RenderTextureDrape | undefined { - const drapedTree = drapedTreeRef.treeOwner.tileTree; - if (undefined === drapedTree) - return undefined; - - const id = drapedTree.modelId; - let drape = this.getTextureDrapeForModel(id); - if (undefined !== drape) - return drape; - - drape = this.viewport.target.getTextureDrape(id); - if (undefined === drape && this.viewport.backgroundDrapeMap) - drape = this.viewport.target.renderSystem.createBackgroundMapDrape(drapedTreeRef, this.viewport.backgroundDrapeMap); - - if (undefined !== drape) - this.textureDrapes.set(id, drape); - - return drape; - } - - /** @internal */ - public getTextureDrapeForModel(modelId: Id64String) { - return this.textureDrapes.get(modelId); - } - - /** @internal */ - public withGraphicType(type: TileGraphicType, func: () => void): void { - const prevType = this._graphicType; - this._graphicType = type; - - func(); - - this._graphicType = prevType; - } - - /** The graphics in the scene that will be drawn with depth. */ - public get graphics() { return this.scene.foreground; } - /** The graphics that will be drawn behind everything else in the scene. */ - public get backgroundGraphics() { return this.scene.background; } - /** The graphics that will be drawn in front of everything else in the scene. */ - public get overlayGraphics() { return this.scene.overlay; } - /** @internal */ - public get planarClassifiers() { return this.scene.planarClassifiers; } - /** @internal */ - public get textureDrapes() { return this.scene.textureDrapes; } - - /** @internal */ - public setVolumeClassifier(classifier: SpatialClassifier, modelId: Id64String): void { - this.scene.volumeClassifier = { classifier, modelId }; - } -} + } + } + + /** @internal */ + public drawStandardGrid(gridOrigin: Point3d, rMatrix: Matrix3d, spacing: XAndY, gridsPerRef: number, _isoGrid: boolean = false, _fixedRepetitions?: Point2d): void { + const vp = this.viewport; + + if (vp.viewingGlobe) + return; + + const color = vp.getContrastToBackgroundColor(); + const planarGrid = this.viewport.target.renderSystem.createPlanarGrid(vp.getFrustum(), { origin: gridOrigin, rMatrix, spacing, gridsPerRef, color }); + if (planarGrid) { + this.addDecoration(GraphicType.WorldDecoration, planarGrid); + } + } + + /** Display skyBox graphic that encompasses entire scene and rotates with camera. + * @see [[RenderSystem.createSkyBox]]. + */ + public setSkyBox(graphic: RenderGraphic) { + this._decorations.skyBox = graphic; + } + + /** Set the graphic to be displayed behind all other geometry as the background of this context's [[ScreenViewport]]. */ + public setViewBackground(graphic: RenderGraphic) { + this._decorations.viewBackground = graphic; + } +} + +/** Context used to create the scene to be drawn in a [[Viewport]]. The scene consists of a set of [[RenderGraphic]]s produced by the + * [[TileTree]]s visible within the viewport. Creating the scene may result in the enqueueing of requests for [[Tile]] content which + * should be displayed in the viewport but are not yet loaded. + * @public + */ +export class SceneContext extends RenderContext { + private _missingChildTiles = false; + /** The graphics comprising the scene. */ + public readonly scene = new Scene(); + + /** @internal */ + public readonly missingTiles = new Set(); + + /** @internal */ + public markChildrenLoading(): void { + this._missingChildTiles = true; + } + + /** @internal */ + public get hasMissingTiles(): boolean { + return this._missingChildTiles || this.missingTiles.size > 0; + } + + private _viewingSpace?: ViewingSpace; + private _graphicType: TileGraphicType = TileGraphicType.Scene; + + public constructor(vp: Viewport, frustum?: Frustum) { + super(vp, frustum); + } + + /** The viewed volume containing the scene. */ + public get viewingSpace(): ViewingSpace { + return undefined !== this._viewingSpace ? this._viewingSpace : this.viewport.viewingSpace; + } + + /** @internal */ + public get graphicType() { return this._graphicType; } + + /** Add the specified graphic to the scene. */ + public outputGraphic(graphic: RenderGraphic): void { + switch (this._graphicType) { + case TileGraphicType.BackgroundMap: + this.backgroundGraphics.push(graphic); + break; + case TileGraphicType.Overlay: + this.overlayGraphics.push(graphic); + break; + default: + this.graphics.push(graphic); + break; + } + } + + /** Indicate that the specified tile is desired for the scene but is not yet ready. A request to load its contents will later be enqueued. */ + public insertMissingTile(tile: Tile): void { + switch (tile.loadStatus) { + case TileLoadStatus.NotLoaded: + case TileLoadStatus.Queued: + case TileLoadStatus.Loading: + this.missingTiles.add(tile); + break; + } + } + + /** @internal */ + public requestMissingTiles(): void { + IModelApp.tileAdmin.requestTiles(this.viewport, this.missingTiles); + } + + /** @internal */ + public addPlanarClassifier(classifiedModelId: Id64String, classifierTree?: SpatialClassifierTileTreeReference, planarClipMask?: PlanarClipMaskState): RenderPlanarClassifier | undefined { + // Target may have the classifier from a previous frame; if not we must create one. + let classifier = this.viewport.target.getPlanarClassifier(classifiedModelId); + if (undefined === classifier) + classifier = this.viewport.target.createPlanarClassifier(classifierTree?.activeClassifier); + + // Either way, we need to collect the graphics to draw for this frame, and record that we did so. + if (undefined !== classifier) { + this.planarClassifiers.set(classifiedModelId, classifier); + classifier.setSource(classifierTree, planarClipMask); + } + + return classifier; + } + + /** @internal */ + public getPlanarClassifierForModel(modelId: Id64String) { + return this.planarClassifiers.get(modelId); + } + + /** @internal */ + public addBackgroundDrapedModel(drapedTreeRef: TileTreeReference, _heightRange: Range1d | undefined): RenderTextureDrape | undefined { + const drapedTree = drapedTreeRef.treeOwner.tileTree; + if (undefined === drapedTree) + return undefined; + + const id = drapedTree.modelId; + let drape = this.getTextureDrapeForModel(id); + if (undefined !== drape) + return drape; + + drape = this.viewport.target.getTextureDrape(id); + if (undefined === drape && this.viewport.backgroundDrapeMap) + drape = this.viewport.target.renderSystem.createBackgroundMapDrape(drapedTreeRef, this.viewport.backgroundDrapeMap); + + if (undefined !== drape) + this.textureDrapes.set(id, drape); + + return drape; + } + + /** @internal */ + public getTextureDrapeForModel(modelId: Id64String) { + return this.textureDrapes.get(modelId); + } + + /** @internal */ + public withGraphicType(type: TileGraphicType, func: () => void): void { + const prevType = this._graphicType; + this._graphicType = type; + + func(); + + this._graphicType = prevType; + } + + /** The graphics in the scene that will be drawn with depth. */ + public get graphics() { return this.scene.foreground; } + /** The graphics that will be drawn behind everything else in the scene. */ + public get backgroundGraphics() { return this.scene.background; } + /** The graphics that will be drawn in front of everything else in the scene. */ + public get overlayGraphics() { return this.scene.overlay; } + /** @internal */ + public get planarClassifiers() { return this.scene.planarClassifiers; } + /** @internal */ + public get textureDrapes() { return this.scene.textureDrapes; } + + /** @internal */ + public setVolumeClassifier(classifier: SpatialClassifier, modelId: Id64String): void { + this.scene.volumeClassifier = { classifier, modelId }; + } +} diff --git a/core/frontend/src/render/GraphicBuilder.ts b/core/frontend/src/render/GraphicBuilder.ts index 19dec8f6ea49..25f966779c02 100644 --- a/core/frontend/src/render/GraphicBuilder.ts +++ b/core/frontend/src/render/GraphicBuilder.ts @@ -147,6 +147,11 @@ export interface GraphicBuilderOptions { * @note Edges will tend to z-fight with their surfaces unless the graphic is [[pickable]]. */ generateEdges?: boolean; + + /** If defined, specifies a point about which the graphic will rotate such that it always faces the viewer. + * @note This has no effect for graphics displayed in a 2d view. + */ + viewIndependentOrigin?: Point3d; } /** Options for creating a [[GraphicBuilder]] to produce a [[RenderGraphic]] to be displayed in a specific [[Viewport]]. diff --git a/core/frontend/src/render/primitives/geometry/GeometryAccumulator.ts b/core/frontend/src/render/primitives/geometry/GeometryAccumulator.ts index 7c645ec719ae..0fa933bdb5ce 100644 --- a/core/frontend/src/render/primitives/geometry/GeometryAccumulator.ts +++ b/core/frontend/src/render/primitives/geometry/GeometryAccumulator.ts @@ -25,6 +25,7 @@ export class GeometryAccumulator { private _transform: Transform; private _surfacesOnly: boolean; private readonly _analysisDisplacement?: AnalysisStyleDisplacement; + private readonly _viewIndependentOrigin?: Point3d; public readonly tileRange: Range3d; public readonly geometries: GeometryList = new GeometryList(); @@ -41,12 +42,14 @@ export class GeometryAccumulator { transform?: Transform; tileRange?: Range3d; analysisStyleDisplacement?: AnalysisStyleDisplacement; + viewIndependentOrigin?: Point3d; }) { this.system = options?.system ?? IModelApp.renderSystem; this.tileRange = options?.tileRange ?? Range3d.createNull(); this._surfacesOnly = true === options?.surfacesOnly; this._transform = options?.transform ?? Transform.createIdentity(); this._analysisDisplacement = options?.analysisStyleDisplacement; + this._viewIndependentOrigin = options?.viewIndependentOrigin; } private getPrimitiveRange(geom: PrimitiveGeometryType): Range3d | undefined { @@ -190,7 +193,7 @@ export class GeometryAccumulator { verts.params.origin.setZero(); - const graphic = mesh.getGraphics(args, this.system); + const graphic = mesh.getGraphics(args, this.system, this._viewIndependentOrigin); if (undefined !== graphic) branch.add(graphic); } diff --git a/core/frontend/src/render/primitives/geometry/GeometryListBuilder.ts b/core/frontend/src/render/primitives/geometry/GeometryListBuilder.ts index 916b9c4a509e..7bcfc555a0a6 100644 --- a/core/frontend/src/render/primitives/geometry/GeometryListBuilder.ts +++ b/core/frontend/src/render/primitives/geometry/GeometryListBuilder.ts @@ -39,6 +39,7 @@ export abstract class GeometryListBuilder extends GraphicBuilder { system, transform: accumulatorTransform, analysisStyleDisplacement: this.analysisStyle?.displacement, + viewIndependentOrigin: options.viewIndependentOrigin, }); } diff --git a/core/frontend/src/render/primitives/mesh/MeshPrimitives.ts b/core/frontend/src/render/primitives/mesh/MeshPrimitives.ts index 1f9f568bb1a7..dd689be06dca 100644 --- a/core/frontend/src/render/primitives/mesh/MeshPrimitives.ts +++ b/core/frontend/src/render/primitives/mesh/MeshPrimitives.ts @@ -7,7 +7,7 @@ */ import { assert } from "@itwin/core-bentley"; -import { AuxChannel, AuxChannelData, Point2d, Range3d } from "@itwin/core-geometry"; +import { AuxChannel, AuxChannelData, Point2d, Point3d, Range3d } from "@itwin/core-geometry"; import { ColorIndex, EdgeArgs, Feature, FeatureIndex, FeatureIndexType, FeatureTable, FillFlags, LinePixels, MeshEdges, MeshPolyline, MeshPolylineList, OctEncodedNormal, PolylineData, PolylineEdgeArgs, PolylineFlags, QParams3d, QPoint3dList, RenderMaterial, RenderTexture, SilhouetteEdgeArgs, @@ -284,12 +284,12 @@ export class Mesh { this.features.toFeatureIndex(index); } - public getGraphics(args: MeshGraphicArgs, system: RenderSystem, instances?: InstancedGraphicParams): RenderGraphic | undefined { + public getGraphics(args: MeshGraphicArgs, system: RenderSystem, instancesOrViewIndependentOrigin?: InstancedGraphicParams | Point3d): RenderGraphic | undefined { if (undefined !== this.triangles && this.triangles.length !== 0) { if (args.meshArgs.init(this)) - return system.createTriMesh(args.meshArgs, instances); + return system.createTriMesh(args.meshArgs, instancesOrViewIndependentOrigin); } else if (undefined !== this.polylines && this.polylines.length !== 0 && args.polylineArgs.init(this)) { - return system.createIndexedPolylines(args.polylineArgs, instances); + return system.createIndexedPolylines(args.polylineArgs, instancesOrViewIndependentOrigin); } return undefined; diff --git a/test-apps/display-test-app/src/frontend/DecorationGeometryExample.ts b/test-apps/display-test-app/src/frontend/DecorationGeometryExample.ts index 5afb835a26b7..0e5e1920401e 100644 --- a/test-apps/display-test-app/src/frontend/DecorationGeometryExample.ts +++ b/test-apps/display-test-app/src/frontend/DecorationGeometryExample.ts @@ -12,9 +12,11 @@ class GeometryDecorator { public readonly useCachedDecorations = true; private readonly _iModel: IModelConnection; private readonly _decorators = new Map void>(); + private readonly _viewIndependentOrigin?: Point3d; - public constructor(viewport: Viewport) { + public constructor(viewport: Viewport, viewIndependentOrigin?: Point3d) { this._iModel = viewport.iModel; + this._viewIndependentOrigin = viewIndependentOrigin; this.addSphere(0); this.addBox(2); @@ -30,7 +32,11 @@ class GeometryDecorator { let colorIndex = 0; const branch = new GraphicBranch(); for (const [key, value] of this._decorators) { - const builder = context.createGraphicBuilder(GraphicType.Scene, undefined, key); + const builder = context.createGraphic({ + type: GraphicType.Scene, + pickable: { id: key }, + viewIndependentOrigin: this._viewIndependentOrigin, + }); const color = colors[colorIndex++]; if (colorIndex >= colors.length) @@ -75,7 +81,8 @@ class GeometryDecorator { } export function openDecorationGeometryExample(viewer: Viewer): void { - IModelApp.viewManager.addDecorator(new GeometryDecorator(viewer.viewport)); + const viewIndependentOrigin = undefined; // new Point3d(4, 0, 0) -- uncomment for testing. + IModelApp.viewManager.addDecorator(new GeometryDecorator(viewer.viewport, viewIndependentOrigin)); assert(viewer.viewport.view.is3d()); viewer.viewport.setStandardRotation(StandardViewId.Iso); From ee6305893c977f76c32581b8b11fb4bbc42c5a99 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Sat, 8 Jan 2022 13:56:50 -0500 Subject: [PATCH 02/10] Extract api. --- common/api/core-frontend.api.md | 1 + ...ilder-view-independent-origin_2022-01-08-18-58.json | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json diff --git a/common/api/core-frontend.api.md b/common/api/core-frontend.api.md index cb21daa57474..bf0f30c259c3 100644 --- a/common/api/core-frontend.api.md +++ b/common/api/core-frontend.api.md @@ -4033,6 +4033,7 @@ export interface GraphicBuilderOptions { placement?: Transform; preserveOrder?: boolean; type: GraphicType; + viewIndependentOrigin?: Point3d; wantNormals?: boolean; } diff --git a/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json b/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json new file mode 100644 index 000000000000..5b7d081ea956 --- /dev/null +++ b/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-frontend", + "comment": "Add support for view-independent decoration graphics.", + "type": "none" + } + ], + "packageName": "@itwin/core-frontend" +} \ No newline at end of file From 68dbfab4c17304c57eaf8ecc4bd7095230b6c57e Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 06:16:48 -0500 Subject: [PATCH 03/10] stub out tests. --- core/frontend/src/test/TestDecorators.ts | 59 +++++++++++++++---- .../src/test/render/webgl/Decorations.test.ts | 17 +++++- .../render/webgl/PickableGraphics.test.ts | 4 +- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/core/frontend/src/test/TestDecorators.ts b/core/frontend/src/test/TestDecorators.ts index b1dba3daf514..7799514b4b7b 100644 --- a/core/frontend/src/test/TestDecorators.ts +++ b/core/frontend/src/test/TestDecorators.ts @@ -9,33 +9,60 @@ import { IModelApp } from "../IModelApp"; import { DecorateContext } from "../ViewContext"; import { ScreenViewport } from "../Viewport"; import { GraphicType, PickableGraphicOptions } from "../render/GraphicBuilder"; +import {GraphicBranch} from "../core-frontend"; /** A simple configurable box decorator for tests. * @internal */ export class BoxDecorator { - public constructor(public readonly vp: ScreenViewport, public readonly color: ColorDef, public readonly pickable?: PickableGraphicOptions, public readonly placement?: Transform, private _shapePoints?: Point3d[]) { - IModelApp.viewManager.addDecorator(this); - } + public viewport: ScreenViewport; + public color: ColorDef; + public pickable?: PickableGraphicOptions; + public placement?: Transform; + public points: Point3d[]; + public viewIndependentOrigin?: Point3d; + public branchTransform?: Transform; - public drop() { - IModelApp.viewManager.dropDecorator(this); - } + public constructor(options: { + viewport: ScreenViewport; + color: ColorDef; + pickable?: PickableGraphicOptions; + placement?: Transform; + points?: Point3d[]; + viewIndependentOrigin?: Point3d; + branchTransform?: Transform; + }) { + this.viewport = options.viewport; + this.color = options.color; + this.pickable = options.pickable; + this.placement = options.placement; + this.viewIndependentOrigin = options.viewIndependentOrigin; + this.branchTransform = options.branchTransform; - public decorate(context: DecorateContext): void { - if (undefined === this._shapePoints) { + if (options.points) { + this.points = options.points; + } else { const w = 0.5; const h = 0.5; - this._shapePoints = [ + this.points = [ new Point3d(0, 0, 0), new Point3d(w, 0, 0), new Point3d(w, h, 0), new Point3d(0, h, 0), new Point3d(0, 0, 0), ]; - this.vp.npcToWorldArray(this._shapePoints); + + this.viewport.npcToWorldArray(this.points); } + IModelApp.viewManager.addDecorator(this); + } + + public drop() { + IModelApp.viewManager.dropDecorator(this); + } + + public decorate(context: DecorateContext): void { const builder = context.createGraphic({ placement: this.placement, type: GraphicType.Scene, @@ -43,8 +70,16 @@ export class BoxDecorator { }); builder.setSymbology(this.color, this.color, 1); - builder.addShape(this._shapePoints); - context.addDecorationFromBuilder(builder); + builder.addShape(this.points); + + let graphic = builder.finish(); + if (this.branchTransform) { + const branch = new GraphicBranch(); + branch.add(graphic); + graphic = context.createBranch(branch, this.branchTransform); + } + + context.addDecoration(GraphicType.Scene, graphic); } } diff --git a/core/frontend/src/test/render/webgl/Decorations.test.ts b/core/frontend/src/test/render/webgl/Decorations.test.ts index 5f56044ed49c..7fb1afad686b 100644 --- a/core/frontend/src/test/render/webgl/Decorations.test.ts +++ b/core/frontend/src/test/render/webgl/Decorations.test.ts @@ -58,7 +58,7 @@ describe("Decorations", () => { afterEach(() => { viewport.dispose(); - for (const decorator of IModelApp.viewManager.decorators.filter((x) => x instanceof BoxDecorator)) + for (const decorator of IModelApp.viewManager.decorators.filter((x) => x instanceof BoxDecorator || x instanceof SphereDecorator)) IModelApp.viewManager.dropDecorator(decorator); }); @@ -69,14 +69,14 @@ describe("Decorations", () => { }); it("draws box decoration in expected location", () => { - const dec = new BoxDecorator(viewport, ColorDef.red, undefined, undefined, shapePoints); + const dec = new BoxDecorator({ viewport, color: ColorDef.red, points: shapePoints }); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); // are both the decorator and background rendering? expectColors(viewport, [dec.color], boxDecLocRect); // is decorator rendering at expected location? dec.drop(); }).timeout(20000); // macOS is slow. it("draws box decoration in graphic-builder-transformed location", () => { - const dec = new BoxDecorator(viewport, ColorDef.red, undefined, Transform.createTranslationXYZ(0.25, 0.25), shapePoints); + const dec = new BoxDecorator({ viewport, color: ColorDef.red, placement: Transform.createTranslationXYZ(0.25, 0.25), points: shapePoints }); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); // are both the decorator and background rendering? expectColors(viewport, [viewport.view.displayStyle.backgroundColor], boxDecLocRect); // background should render where the decorator would have been without transform. dec.drop(); @@ -95,4 +95,15 @@ describe("Decorations", () => { expectColors(viewport, [dec.color], sphereDecBgLocRect); // when sphere is transformed, this location should contain the sphere dec.drop(); }).timeout(20000); // macOS is slow. + + describe("view-independent origin", () => { + it("rotates about center", () => { + }); + + it("rotates about corner", () => { + }); + + it("applies branch transform to origin", () => { + }); + }); }); diff --git a/core/frontend/src/test/render/webgl/PickableGraphics.test.ts b/core/frontend/src/test/render/webgl/PickableGraphics.test.ts index e07d4d4d4b10..4f7aeb7daf25 100644 --- a/core/frontend/src/test/render/webgl/PickableGraphics.test.ts +++ b/core/frontend/src/test/render/webgl/PickableGraphics.test.ts @@ -65,14 +65,14 @@ describe("Pickable graphic", () => { } it("is pickable", () => { - const dec = new BoxDecorator(viewport, ColorDef.red, { id: "0x123", locateOnly: false }); + const dec = new BoxDecorator({ viewport, color: ColorDef.red, pickable: { id: "0x123", locateOnly: false } }); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); expect(dec.pickable).to.not.be.undefined; expectIds([dec.pickable!.id]); }).timeout(20000); // macOS is slow. it("optionally draws only for pick", () => { - const dec = new BoxDecorator(viewport, ColorDef.blue, { id: "0x456", locateOnly: true }); + const dec = new BoxDecorator({ viewport, color: ColorDef.blue, pickable: { id: "0x456", locateOnly: true } }); expectColors(viewport, [viewport.view.displayStyle.backgroundColor]); expect(dec.pickable).to.not.be.undefined; expectIds([dec.pickable!.id]); From c64c0664b9c14dab31ae07eca57f667f5808839d Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 07:40:13 -0500 Subject: [PATCH 04/10] further test cleanup; wip tests. --- core/frontend/src/test/ExpectColors.ts | 16 ++++++------- .../src/test/render/webgl/Decorations.test.ts | 23 +++++++++++++------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/frontend/src/test/ExpectColors.ts b/core/frontend/src/test/ExpectColors.ts index ffbcf948e457..6c4a05ff48ea 100644 --- a/core/frontend/src/test/ExpectColors.ts +++ b/core/frontend/src/test/ExpectColors.ts @@ -17,20 +17,18 @@ export function expectColors(viewport: ScreenViewport, expected: ColorDef[], vie expect(buf).not.to.be.undefined; const u32 = new Uint32Array(buf.data.buffer); - const values = new Set(); - for (const rgba of u32) - values.add(rgba); - - expect(values.size).to.equal(expected.length); - - for (const rgba of values) { + const actualColors = new Set(); + for (const rgba of u32) { const r = ((rgba & 0x000000ff) >>> 0x00) >>> 0; const g = ((rgba & 0x0000ff00) >>> 0x08) >>> 0; const b = ((rgba & 0x00ff0000) >>> 0x10) >>> 0; const a = ((rgba & 0xff000000) >>> 0x18) >>> 0; - const actualColor = ColorDef.from(r, g, b, 0xff - a); - expect(expected.findIndex((x) => x.tbgr === actualColor.tbgr)).least(0); + actualColors.add(ColorDef.from(r, g, b, 0xff - a)); } + + const expectedTbgr = expected.map((x) => x.tbgr.toString(16)).sort(); + const actualTbgr = Array.from(actualColors).map((x) => x.tbgr.toString(16)).sort(); + expect(actualTbgr).to.deep.equal(expectedTbgr); } /** A viewport-color-checking function for tests. Tests for the presence of a list of any unexpected colors in the entire viewport or specified ViewRect. If any of the colors are found, this function expects them not to be found and will fail the test. diff --git a/core/frontend/src/test/render/webgl/Decorations.test.ts b/core/frontend/src/test/render/webgl/Decorations.test.ts index 7fb1afad686b..edddf5de677b 100644 --- a/core/frontend/src/test/render/webgl/Decorations.test.ts +++ b/core/frontend/src/test/render/webgl/Decorations.test.ts @@ -12,6 +12,8 @@ import { createBlankConnection } from "../../createBlankConnection"; import { BoxDecorator, SphereDecorator } from "../../TestDecorators"; import { expectColors } from "../../ExpectColors"; import { ViewRect } from "../../../ViewRect"; +import { ViewState } from "../../../ViewState"; +import { StandardViewId } from "../../../StandardView"; describe("Decorations", () => { let imodel: IModelConnection; @@ -21,13 +23,11 @@ describe("Decorations", () => { let boxDecLocRect: ViewRect; let sphereDecBgLocRect: ViewRect; - const w = 0.5; - const h = 0.5; const shapePoints = [ new Point3d(0, 0, 0), - new Point3d(w, 0, 0), - new Point3d(w, h, 0), - new Point3d(0, h, 0), + new Point3d(0.5, 0, 0), + new Point3d(0.5, 0.5, 0), + new Point3d(0, 0.5, 0), new Point3d(0, 0, 0), ]; @@ -52,7 +52,7 @@ describe("Decorations", () => { viewport = ScreenViewport.create(div, view); width = viewport.viewRect.width; height = viewport.viewRect.height; - boxDecLocRect = new ViewRect(0, 0, 1, 1); + boxDecLocRect = new ViewRect(0, 0, 10, 10); sphereDecBgLocRect = new ViewRect(width - 2, height / 2 + 128, width - 2 + 1, height / 2 + 128 + 1); }); @@ -97,7 +97,16 @@ describe("Decorations", () => { }).timeout(20000); // macOS is slow. describe("view-independent origin", () => { - it("rotates about center", () => { + it("rotates about top-right corner", () => { + const viewIndependentOrigin = new Point3d(0.5, 0.5, 0); + const dec = new BoxDecorator({ viewport, color: ColorDef.red, points: shapePoints, viewIndependentOrigin }); + expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); + expectColors(viewport, [dec.color], boxDecLocRect); + + viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Front), viewIndependentOrigin); + viewport.synchWithView(); + expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); + expectColors(viewport, [dec.color], boxDecLocRect); }); it("rotates about corner", () => { From 40a1bc2525f54ca848fedc1585695013c9256181 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 08:28:19 -0500 Subject: [PATCH 05/10] Account for inverted Y in Viewport.readPixels (needs to be fixed). --- .../src/test/render/webgl/Decorations.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/frontend/src/test/render/webgl/Decorations.test.ts b/core/frontend/src/test/render/webgl/Decorations.test.ts index edddf5de677b..9c2305f5929f 100644 --- a/core/frontend/src/test/render/webgl/Decorations.test.ts +++ b/core/frontend/src/test/render/webgl/Decorations.test.ts @@ -52,7 +52,7 @@ describe("Decorations", () => { viewport = ScreenViewport.create(div, view); width = viewport.viewRect.width; height = viewport.viewRect.height; - boxDecLocRect = new ViewRect(0, 0, 10, 10); + boxDecLocRect = new ViewRect(0, 0, width / 2, height / 2); sphereDecBgLocRect = new ViewRect(width - 2, height / 2 + 128, width - 2 + 1, height / 2 + 128 + 1); }); @@ -78,7 +78,7 @@ describe("Decorations", () => { it("draws box decoration in graphic-builder-transformed location", () => { const dec = new BoxDecorator({ viewport, color: ColorDef.red, placement: Transform.createTranslationXYZ(0.25, 0.25), points: shapePoints }); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); // are both the decorator and background rendering? - expectColors(viewport, [viewport.view.displayStyle.backgroundColor], boxDecLocRect); // background should render where the decorator would have been without transform. + expectColors(viewport, [viewport.view.displayStyle.backgroundColor], new ViewRect(0, 0, 10, 10)); // background should render where the decorator would have been without transform. dec.drop(); }).timeout(20000); // macOS is slow. @@ -97,16 +97,20 @@ describe("Decorations", () => { }).timeout(20000); // macOS is slow. describe("view-independent origin", () => { + // ###TODO: Viewport.readImage is broken. It accepts a ViewRect which defines the origin at the top-left, and passes it to gl.readPixels, which + // defines the origin at the bottom-left. Update these tests after fixing that. it("rotates about top-right corner", () => { const viewIndependentOrigin = new Point3d(0.5, 0.5, 0); const dec = new BoxDecorator({ viewport, color: ColorDef.red, points: shapePoints, viewIndependentOrigin }); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); expectColors(viewport, [dec.color], boxDecLocRect); - viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Front), viewIndependentOrigin); + const w = viewport.viewRect.width; + const h = viewport.viewRect.height; + viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Bottom), viewIndependentOrigin); viewport.synchWithView(); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); - expectColors(viewport, [dec.color], boxDecLocRect); + expectColors(viewport, [dec.color], new ViewRect(0, h / 2, w / 2, h)); }); it("rotates about corner", () => { From 77c14b25e138610d3cf5011d97e02fc0d6722f0e Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 08:33:40 -0500 Subject: [PATCH 06/10] apply view-independent origin. --- core/frontend/src/test/TestDecorators.ts | 1 + .../src/test/render/webgl/Decorations.test.ts | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/frontend/src/test/TestDecorators.ts b/core/frontend/src/test/TestDecorators.ts index 7799514b4b7b..ae605440c9d1 100644 --- a/core/frontend/src/test/TestDecorators.ts +++ b/core/frontend/src/test/TestDecorators.ts @@ -67,6 +67,7 @@ export class BoxDecorator { placement: this.placement, type: GraphicType.Scene, pickable: this.pickable, + viewIndependentOrigin: this.viewIndependentOrigin, }); builder.setSymbology(this.color, this.color, 1); diff --git a/core/frontend/src/test/render/webgl/Decorations.test.ts b/core/frontend/src/test/render/webgl/Decorations.test.ts index 9c2305f5929f..3814ba6007fd 100644 --- a/core/frontend/src/test/render/webgl/Decorations.test.ts +++ b/core/frontend/src/test/render/webgl/Decorations.test.ts @@ -105,18 +105,21 @@ describe("Decorations", () => { expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); expectColors(viewport, [dec.color], boxDecLocRect); - const w = viewport.viewRect.width; - const h = viewport.viewRect.height; viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Bottom), viewIndependentOrigin); viewport.synchWithView(); expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); - expectColors(viewport, [dec.color], new ViewRect(0, h / 2, w / 2, h)); - }); + expectColors(viewport, [dec.color], boxDecLocRect); + + viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Front), viewIndependentOrigin); + viewport.synchWithView(); + expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); + expectColors(viewport, [dec.color], boxDecLocRect); + }).timeout(20000); it("rotates about corner", () => { - }); + }).timeout(20000); it("applies branch transform to origin", () => { - }); + }).timeout(20000); }); }); From 5b1826d4561d5d22bab48a8fd31c7d36c31ada21 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 08:34:45 -0500 Subject: [PATCH 07/10] Finish test. --- .../src/test/render/webgl/Decorations.test.ts | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/core/frontend/src/test/render/webgl/Decorations.test.ts b/core/frontend/src/test/render/webgl/Decorations.test.ts index 3814ba6007fd..0f7292e3d029 100644 --- a/core/frontend/src/test/render/webgl/Decorations.test.ts +++ b/core/frontend/src/test/render/webgl/Decorations.test.ts @@ -96,30 +96,22 @@ describe("Decorations", () => { dec.drop(); }).timeout(20000); // macOS is slow. - describe("view-independent origin", () => { - // ###TODO: Viewport.readImage is broken. It accepts a ViewRect which defines the origin at the top-left, and passes it to gl.readPixels, which - // defines the origin at the bottom-left. Update these tests after fixing that. - it("rotates about top-right corner", () => { - const viewIndependentOrigin = new Point3d(0.5, 0.5, 0); - const dec = new BoxDecorator({ viewport, color: ColorDef.red, points: shapePoints, viewIndependentOrigin }); - expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); - expectColors(viewport, [dec.color], boxDecLocRect); - - viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Bottom), viewIndependentOrigin); - viewport.synchWithView(); - expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); - expectColors(viewport, [dec.color], boxDecLocRect); - - viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Front), viewIndependentOrigin); - viewport.synchWithView(); - expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); - expectColors(viewport, [dec.color], boxDecLocRect); - }).timeout(20000); - - it("rotates about corner", () => { - }).timeout(20000); - - it("applies branch transform to origin", () => { - }).timeout(20000); - }); + // ###TODO: Viewport.readImage is broken. It accepts a ViewRect which defines the origin at the top-left, and passes it to gl.readPixels, which + // defines the origin at the bottom-left. Update these tests after fixing that. + it("rotates about view-independent origin", () => { + const viewIndependentOrigin = new Point3d(0.5, 0.5, 0); + const dec = new BoxDecorator({ viewport, color: ColorDef.red, points: shapePoints, viewIndependentOrigin }); + expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); + expectColors(viewport, [dec.color], boxDecLocRect); + + viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Bottom), viewIndependentOrigin); + viewport.synchWithView(); + expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); + expectColors(viewport, [dec.color], boxDecLocRect); + + viewport.view.setRotationAboutPoint(ViewState.getStandardViewMatrix(StandardViewId.Front), viewIndependentOrigin); + viewport.synchWithView(); + expectColors(viewport, [dec.color, viewport.view.displayStyle.backgroundColor]); + expectColors(viewport, [dec.color], boxDecLocRect); + }).timeout(20000); }); From 2c3d0841894cab9546cba907ff250f13bbfc5600 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 08:38:27 -0500 Subject: [PATCH 08/10] comment. --- core/frontend/src/render/GraphicBuilder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/frontend/src/render/GraphicBuilder.ts b/core/frontend/src/render/GraphicBuilder.ts index 25f966779c02..e4ad5a61260b 100644 --- a/core/frontend/src/render/GraphicBuilder.ts +++ b/core/frontend/src/render/GraphicBuilder.ts @@ -149,6 +149,7 @@ export interface GraphicBuilderOptions { generateEdges?: boolean; /** If defined, specifies a point about which the graphic will rotate such that it always faces the viewer. + * This can be particular useful for planar regions to create a billboarding effect - e.g., to implement [[Marker]]-like WebGL decorations. * @note This has no effect for graphics displayed in a 2d view. */ viewIndependentOrigin?: Point3d; From c76af3131d869f02b72bdb3e5aa61781c21e2c16 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 08:54:37 -0500 Subject: [PATCH 09/10] just build my code please --- ...phic-builder-view-independent-origin_2022-01-08-18-58.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json b/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json index 5b7d081ea956..dd22aafb1f06 100644 --- a/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json +++ b/common/changes/@itwin/core-frontend/graphic-builder-view-independent-origin_2022-01-08-18-58.json @@ -2,9 +2,9 @@ "changes": [ { "packageName": "@itwin/core-frontend", - "comment": "Add support for view-independent decoration graphics.", + "comment": "Added support for view-independent decoration graphics.", "type": "none" } ], "packageName": "@itwin/core-frontend" -} \ No newline at end of file +} From 526df187a7aa8f428a88388d02dd08ebbacf3cd6 Mon Sep 17 00:00:00 2001 From: Paul Connelly <22944042+pmconne@users.noreply.github.com> Date: Mon, 10 Jan 2022 09:15:04 -0500 Subject: [PATCH 10/10] origin coord note. --- core/frontend/src/render/GraphicBuilder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/frontend/src/render/GraphicBuilder.ts b/core/frontend/src/render/GraphicBuilder.ts index e4ad5a61260b..c366ede7eeba 100644 --- a/core/frontend/src/render/GraphicBuilder.ts +++ b/core/frontend/src/render/GraphicBuilder.ts @@ -150,6 +150,7 @@ export interface GraphicBuilderOptions { /** If defined, specifies a point about which the graphic will rotate such that it always faces the viewer. * This can be particular useful for planar regions to create a billboarding effect - e.g., to implement [[Marker]]-like WebGL decorations. + * The graphic's [[placement]] transform is not applied to the point. * @note This has no effect for graphics displayed in a 2d view. */ viewIndependentOrigin?: Point3d;