From b42bedd446bbadf87cd57f7bbd0f276e90c335a5 Mon Sep 17 00:00:00 2001 From: andremig-bentley <101671244+andremig-bentley@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:25:19 -0600 Subject: [PATCH] Add disableClipStyle to BranchState (#7109) Co-authored-by: andremig-bentley Co-authored-by: Ben Polinsky <78756012+ben-polinsky@users.noreply.github.com> Co-authored-by: Paul Connelly <22944042+pmconne@users.noreply.github.com> --- common/api/core-frontend.api.md | 5 +- ...dremig-clipstyle-bug_2024-08-26-17-00.json | 10 + core/frontend/src/render/GraphicBranch.ts | 4 + .../src/render/RealityMeshGraphicParams.ts | 1 + core/frontend/src/render/RenderSystem.ts | 4 +- core/frontend/src/render/webgl/BranchState.ts | 6 + .../src/render/webgl/BranchUniforms.ts | 16 ++ core/frontend/src/render/webgl/Graphic.ts | 2 + core/frontend/src/render/webgl/RealityMesh.ts | 2 +- .../test/render/webgl/BranchUniforms.test.ts | 230 +++++++++++++++++- core/frontend/src/tile/map/MapTile.ts | 2 +- 11 files changed, 272 insertions(+), 10 deletions(-) create mode 100644 common/changes/@itwin/core-frontend/andremig-clipstyle-bug_2024-08-26-17-00.json diff --git a/common/api/core-frontend.api.md b/common/api/core-frontend.api.md index 13d0e22ca1ca..dbd5ee08dde3 100644 --- a/common/api/core-frontend.api.md +++ b/common/api/core-frontend.api.md @@ -4562,6 +4562,7 @@ export interface GraphicBranchOptions { // @internal (undocumented) classifierOrDrape?: RenderPlanarClassifier | RenderTextureDrape; clipVolume?: RenderClipVolume; + disableClipStyle?: true; // @internal (undocumented) frustum?: GraphicBranchFrustum; hline?: HiddenLine.Settings; @@ -9069,6 +9070,8 @@ export interface RealityMeshGraphicParams { // (undocumented) readonly baseTransparent: boolean; // (undocumented) + readonly disableClipStyle?: true; + // (undocumented) readonly featureTable: PackedFeatureTable; // (undocumented) readonly layerClassifiers?: MapLayerClassifiers; @@ -10022,7 +10025,7 @@ export abstract class RenderSystem implements IDisposable { // @internal (undocumented) createBackgroundMapDrape(_drapedTree: TileTreeReference, _mapTree: MapTileTreeReference): RenderTextureDrape | undefined; abstract createBatch(graphic: RenderGraphic, features: RenderFeatureTable, range: ElementAlignedBox3d, options?: BatchOptions): RenderGraphic; - createBranch(branch: GraphicBranch, transform: Transform): RenderGraphic; + createBranch(branch: GraphicBranch, transform: Transform, options?: GraphicBranchOptions): RenderGraphic; createClipVolume(_clipVector: ClipVector): RenderClipVolume | undefined; // @internal (undocumented) createGeometryFromMesh(mesh: Mesh, viOrigin: Point3d | undefined): RenderGeometry | undefined; diff --git a/common/changes/@itwin/core-frontend/andremig-clipstyle-bug_2024-08-26-17-00.json b/common/changes/@itwin/core-frontend/andremig-clipstyle-bug_2024-08-26-17-00.json new file mode 100644 index 000000000000..ac11a63efe7e --- /dev/null +++ b/common/changes/@itwin/core-frontend/andremig-clipstyle-bug_2024-08-26-17-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-frontend", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-frontend" +} \ No newline at end of file diff --git a/core/frontend/src/render/GraphicBranch.ts b/core/frontend/src/render/GraphicBranch.ts index d4c22949d8f2..c0d22230b0ea 100644 --- a/core/frontend/src/render/GraphicBranch.ts +++ b/core/frontend/src/render/GraphicBranch.ts @@ -160,6 +160,10 @@ export interface GraphicBranchOptions { * @internal */ viewAttachmentId?: Id64String; + /** If true, the view's [DisplayStyleSettings.clipStyle]($common) will be disabled for this branch. + * No [ClipStyle.insideColor]($common), [ClipStyle.outsideColor]($common), or [ClipStyle.intersectionStyle]($common) will be applied. + */ + disableClipStyle?: true; } /** Clip/Transform for a branch that are varied over time. diff --git a/core/frontend/src/render/RealityMeshGraphicParams.ts b/core/frontend/src/render/RealityMeshGraphicParams.ts index f6b5ffdc756a..7152c60f5e86 100644 --- a/core/frontend/src/render/RealityMeshGraphicParams.ts +++ b/core/frontend/src/render/RealityMeshGraphicParams.ts @@ -21,4 +21,5 @@ export interface RealityMeshGraphicParams { readonly baseTransparent: boolean; readonly textures?: TerrainTexture[]; readonly layerClassifiers?: MapLayerClassifiers; + readonly disableClipStyle?: true; } diff --git a/core/frontend/src/render/RenderSystem.ts b/core/frontend/src/render/RenderSystem.ts index ab8b201ca703..799873883be6 100644 --- a/core/frontend/src/render/RenderSystem.ts +++ b/core/frontend/src/render/RenderSystem.ts @@ -610,8 +610,8 @@ export abstract class RenderSystem implements IDisposable { public abstract createGraphicList(primitives: RenderGraphic[]): RenderGraphic; /** Create a RenderGraphic consisting of a list of Graphics, with optional transform and symbology overrides applied to the list */ - public createBranch(branch: GraphicBranch, transform: Transform): RenderGraphic { - return this.createGraphicBranch(branch, transform); + public createBranch(branch: GraphicBranch, transform: Transform, options?: GraphicBranchOptions): RenderGraphic { + return this.createGraphicBranch(branch, transform, options); } /** Create a graphic from a [[GraphicBranch]]. */ diff --git a/core/frontend/src/render/webgl/BranchState.ts b/core/frontend/src/render/webgl/BranchState.ts index db5683481743..08159b738c32 100644 --- a/core/frontend/src/render/webgl/BranchState.ts +++ b/core/frontend/src/render/webgl/BranchState.ts @@ -46,6 +46,10 @@ export interface BranchStateOptions { forceViewCoords?: boolean; readonly viewAttachmentId?: Id64String; groupNodeId?: number; + /** If true, the view's [DisplayStyleSettings.clipStyle]($common) will be disabled for this branch. + * No [ClipStyle.insideColor]($common), [ClipStyle.outsideColor]($common), or [ClipStyle.intersectionStyle]($common) will be applied. + */ + disableClipStyle?: true; } /** @@ -73,6 +77,7 @@ export class BranchState { public get realityModelDisplaySettings() { return this._opts.realityModelDisplaySettings; } public get viewAttachmentId() { return this._opts.viewAttachmentId; } public get groupNodeId() { return this._opts.groupNodeId; } + public get disableClipStyle() { return this._opts.disableClipStyle;} public get symbologyOverrides() { return this._opts.symbologyOverrides; @@ -108,6 +113,7 @@ export class BranchState { realityModelDisplaySettings: branch.branch.realityModelDisplaySettings ?? prev.realityModelDisplaySettings, viewAttachmentId: branch.viewAttachmentId ?? prev.viewAttachmentId, groupNodeId: branch.branch.groupNodeId ?? prev.groupNodeId, + disableClipStyle: branch.disableClipStyle ?? prev.disableClipStyle, }); } diff --git a/core/frontend/src/render/webgl/BranchUniforms.ts b/core/frontend/src/render/webgl/BranchUniforms.ts index b8c815f75c7a..6cda93b44131 100644 --- a/core/frontend/src/render/webgl/BranchUniforms.ts +++ b/core/frontend/src/render/webgl/BranchUniforms.ts @@ -21,6 +21,7 @@ import { RenderCommands } from "./RenderCommands"; import { desync, sync, SyncToken } from "./Sync"; import { Target } from "./Target"; import { ClipStack } from "./ClipStack"; +import { IModelApp } from "../../IModelApp"; function equalXYZs(a: XYZ | undefined, b: XYZ | undefined): boolean { if (a === b) @@ -103,6 +104,9 @@ export class BranchUniforms { public pushBranch(branch: Branch): void { desync(this); this._stack.pushBranch(branch); + + this.setClipStyle(this.top.disableClipStyle); + if (this.top.clipVolume) this.clipStack.push(this.top.clipVolume); @@ -126,6 +130,7 @@ export class BranchUniforms { this.clipStack.pop(); this._stack.pop(); + this.setClipStyle(this.top.disableClipStyle); } public pushViewClip(): void { @@ -251,4 +256,15 @@ export class BranchUniforms { return true; } + + // set the clip style based on disableClipStyle + private setClipStyle(disableClipStyle: true | undefined) { + const vp = IModelApp.viewManager.selectedView; + if (vp) { + const style = vp.view.displayStyle.settings.clipStyle; + this.clipStack.insideColor.alpha = disableClipStyle ? 0 : (style.insideColor ? 1 : 0); + this.clipStack.outsideColor.alpha = disableClipStyle ? 0 : (style.outsideColor ? 1 : 0); + this.clipStack.intersectionStyle.alpha = disableClipStyle ? 0 : (style.intersectionStyle ? style.intersectionStyle.width : 0); + } + } } diff --git a/core/frontend/src/render/webgl/Graphic.ts b/core/frontend/src/render/webgl/Graphic.ts index 2b6361c6be2e..d768ee62b1e3 100644 --- a/core/frontend/src/render/webgl/Graphic.ts +++ b/core/frontend/src/render/webgl/Graphic.ts @@ -315,6 +315,7 @@ export class Branch extends Graphic { public readonly appearanceProvider?: FeatureAppearanceProvider; public readonly secondaryClassifiers?: PlanarClassifier[]; public readonly viewAttachmentId?: Id64String; + public disableClipStyle?: true; public readonly transformFromExternalIModel?: Transform; public constructor(branch: GraphicBranch, localToWorld: Transform, viewFlags?: ViewFlags, opts?: GraphicBranchOptions) { @@ -333,6 +334,7 @@ export class Branch extends Graphic { this.iModel = opts.iModel; this.frustum = opts.frustum; this.viewAttachmentId = opts.viewAttachmentId; + this.disableClipStyle = opts.disableClipStyle; this.transformFromExternalIModel = opts.transformFromIModel; if (opts.hline) diff --git a/core/frontend/src/render/webgl/RealityMesh.ts b/core/frontend/src/render/webgl/RealityMesh.ts index 21cd2d9e3231..2e51a0b961a7 100644 --- a/core/frontend/src/render/webgl/RealityMesh.ts +++ b/core/frontend/src/render/webgl/RealityMesh.ts @@ -384,7 +384,7 @@ export class RealityMeshGeometry extends IndexedGeometry implements RenderGeomet branch.add(system.createBatch(primitive!, featureTable, mesh.getRange(), { tileId })); } - return system.createBranch(branch, realityMesh._transform ? realityMesh._transform : Transform.createIdentity()); + return system.createBranch(branch, realityMesh._transform ? realityMesh._transform : Transform.createIdentity(), {disableClipStyle: params.disableClipStyle}); } public collectStatistics(stats: RenderMemory.Statistics): void { diff --git a/core/frontend/src/test/render/webgl/BranchUniforms.test.ts b/core/frontend/src/test/render/webgl/BranchUniforms.test.ts index ec076fbd0b6e..ddaf3355cd40 100644 --- a/core/frontend/src/test/render/webgl/BranchUniforms.test.ts +++ b/core/frontend/src/test/render/webgl/BranchUniforms.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { expect } from "chai"; import { dispose } from "@itwin/core-bentley"; -import { ClipVector, Point3d, Transform } from "@itwin/core-geometry"; +import { ClipVector, Point3d, Transform, Vector3d } from "@itwin/core-geometry"; import { IModelApp } from "../../../IModelApp"; import { ViewRect } from "../../../common/ViewRect"; import { createEmptyRenderPlan } from "../../../render/RenderPlan"; @@ -13,7 +13,11 @@ import { Branch } from "../../../render/webgl/Graphic"; import { ClipVolume } from "../../../render/webgl/ClipVolume"; import { ClipStack } from "../../../render/webgl/ClipStack"; import { Target } from "../../../render/webgl/Target"; -import { EmptyLocalization } from "@itwin/core-common"; +import { ClipStyle, EmptyLocalization } from "@itwin/core-common"; +import { BranchUniforms } from "../../../render/webgl/BranchUniforms"; +import { ScreenViewport } from "../../../Viewport"; +import { SpatialViewState } from "../../../core-frontend"; +import { createBlankConnection } from "../../createBlankConnection"; function makeClipVolume(): ClipVolume { const vec = ClipVector.createEmpty(); @@ -26,14 +30,14 @@ function makeClipVolume(): ClipVolume { interface ClipInfo { clip?: ClipVolume; noViewClip?: boolean; // undefined means inherit from parent on branch stack + disableClipStyle?: true; } function makeBranch(info: ClipInfo): Branch { const branch = new GraphicBranch(); if (undefined !== info.noViewClip) branch.viewFlagOverrides.clipVolume = !info.noViewClip; - - const graphic = IModelApp.renderSystem.createGraphicBranch(branch, Transform.identity, { clipVolume: info.clip }); + const graphic = IModelApp.renderSystem.createGraphicBranch(branch, Transform.identity, { clipVolume: info.clip, disableClipStyle: info.disableClipStyle }); expect(graphic instanceof Branch).to.be.true; return graphic as Branch; } @@ -59,13 +63,21 @@ function expectClipStack(target: Target, expected: Array<{ numRows: number }>): expect(actual[i]).to.equal(expected[i]); } +function expectClipStyle(uniforms: BranchUniforms, expectedAlphas: number[]): void { + expect(uniforms.clipStack.insideColor.alpha).to.equal(expectedAlphas[0]); + expect(uniforms.clipStack.outsideColor.alpha).to.equal(expectedAlphas[1]); + expect(uniforms.clipStack.intersectionStyle.alpha).to.equal(expectedAlphas[2]); +} + /** Inputs: * - The view clip and ViewFlags.clipVolume * - A stack of branches to be pushed * - The expected stack of ClipVolumes on the ClipStack, including the view clip, after pushing all branches. * - Whether we expect the view's clip to apply to the top branch. + * - Optionally, the Clipstack's expected ClipStyle alpha values after the branches are pushed. + * - Optionally, the viewport's ClipStyle alpha values - what is expected after the branches are popped. */ -function testBranches(viewClip: ClipInfo, branches: ClipInfo[], expectViewClip: boolean, expectedClips: Array<{ numRows: number }>): void { +function testBranches(viewClip: ClipInfo, branches: ClipInfo[], expectViewClip: boolean, expectedClips: Array<{ numRows: number }>, branchClipStyleAlphaValues?: number[], viewportClipStyleAlphaValues?: number[]): void { const plan = { ...createEmptyRenderPlan(), clip: viewClip.clip?.clipVector }; plan.viewFlags = plan.viewFlags.with("clipVolume", true !== viewClip.noViewClip); @@ -86,9 +98,15 @@ function testBranches(viewClip: ClipInfo, branches: ClipInfo[], expectViewClip: expect(uniforms.clipStack.hasClip).to.equal(expectViewClip || expectedClips.length > 1); expectClipStack(target, expectedClips); + if (branchClipStyleAlphaValues) + expectClipStyle(uniforms, branchClipStyleAlphaValues); + for (const _branch of branches) target.popBranch(); + if (viewportClipStyleAlphaValues) + expectClipStyle(uniforms, viewportClipStyleAlphaValues); + expect(uniforms.clipStack.hasViewClip).to.equal(hadViewClip); expect(uniforms.clipStack.hasClip).to.equal(hadClip); expectClipStack(target, prevClips); @@ -140,4 +158,206 @@ describe("BranchUniforms", async () => { testBranches({ clip: viewClip }, [{ clip: outerClip }, { clip: innerClip }], true, [viewClip, outerClip, innerClip]); }); + + it ("should disable clip style", async () => { + const viewClip = makeClipVolume(); + const branchClip = makeClipVolume(); + + // create a viewport + const imodel = createBlankConnection("imodel"); + const viewDiv = document.createElement("div"); + viewDiv.style.width = viewDiv.style.height = "100px"; + document.body.appendChild(viewDiv); + const view = SpatialViewState.createBlank(imodel, new Point3d(), new Vector3d(1, 1, 1)); + const vp = ScreenViewport.create(viewDiv, view); + IModelApp.viewManager.addViewport(vp); + await IModelApp.viewManager.setSelectedView(vp); + + // create a clip style and assign it to the viewport + const testStyle = ClipStyle.fromJSON({ + insideColor: {r: 255, g: 0, b: 0}, + outsideColor: {r: 0, g: 255, b: 0}, + intersectionStyle: { + color: {r: 0, g: 0, b: 255}, + width: 1, + }, + }); + vp.clipStyle = testStyle; + + // disableClipStyle is undefined, so we expect the inside color, outside color, and intersection style width to all be 1 + testBranches({ clip: viewClip }, [{ clip: branchClip }], true, [viewClip, branchClip], [1,1,1]); + + // disableClipStyle is true, so we expect the branch to have disabled the inside color, outside color, and intersection style width, + // setting all of their alpha values to 0. After the branch is popped, we expect the viewport's clip style to be restored. + testBranches({ clip: viewClip }, [{ clip: branchClip, disableClipStyle: true }], true, [viewClip, branchClip], [0,0,0], [1,1,1]); + + IModelApp.viewManager.dropViewport(vp); + }); + + it("should inherit clip style from the top of the stack",async () => { + const viewClip = makeClipVolume(); + const firstClip = makeClipVolume(); + const secondClip = makeClipVolume(); + const thirdClip = makeClipVolume(); + + // create a viewport + const imodel = createBlankConnection("imodel"); + const viewDiv = document.createElement("div"); + viewDiv.style.width = viewDiv.style.height = "100px"; + document.body.appendChild(viewDiv); + const view = SpatialViewState.createBlank(imodel, new Point3d(), new Vector3d(1, 1, 1)); + const vp = ScreenViewport.create(viewDiv, view); + IModelApp.viewManager.addViewport(vp); + await IModelApp.viewManager.setSelectedView(vp); + + // create a clip style and assign it to the viewport + const testStyle = ClipStyle.fromJSON({ + insideColor: {r: 255, g: 0, b: 0}, + outsideColor: {r: 0, g: 255, b: 0}, + intersectionStyle: { + color: {r: 0, g: 0, b: 255}, + width: 1, + }, + }); + vp.clipStyle = testStyle; + + testBranches({ clip: viewClip }, [{ clip: firstClip, disableClipStyle: true }, { clip: secondClip}, {clip: thirdClip}], true, [viewClip, firstClip, secondClip, thirdClip], [0,0,0], [1,1,1]); + + IModelApp.viewManager.dropViewport(vp); + }); + + it("should disable clip style in complex scene graphs", async () => { + + // create a viewport + const imodel = createBlankConnection("imodel"); + const viewDiv = document.createElement("div"); + viewDiv.style.width = viewDiv.style.height = "100px"; + document.body.appendChild(viewDiv); + const view = SpatialViewState.createBlank(imodel, new Point3d(), new Vector3d(1, 1, 1)); + const vp = ScreenViewport.create(viewDiv, view); + IModelApp.viewManager.addViewport(vp); + await IModelApp.viewManager.setSelectedView(vp); + + // create a clip style and assign it to the viewport + const testStyle = ClipStyle.fromJSON({ + insideColor: {r: 255, g: 0, b: 0}, + outsideColor: {r: 0, g: 255, b: 0}, + intersectionStyle: { + color: {r: 0, g: 0, b: 255}, + width: 1, + }, + }); + vp.clipStyle = testStyle; + + // create target + const viewClip = makeClipVolume(); + const plan = { ...createEmptyRenderPlan(), clip: viewClip.clipVector }; + plan.viewFlags = plan.viewFlags.with("clipVolume", true); + const target = makeTarget(); + target.changeRenderPlan(plan); + target.pushViewClip(); + + const uniforms = target.uniforms.branch; + + /* scenario 1 scene graph: + * branch 1 + * branch 1a: disableClipStyle = true; + * branch 1a1 + * branch 1a2 + * + * clip style should be disabled for branches 1a, 1a1, and 1a2 + */ + + const branch1 = makeBranch({ clip: makeClipVolume() }); + const branch1a = makeBranch({ clip: makeClipVolume(), disableClipStyle: true }); + const branch1a1 = makeBranch({ clip: makeClipVolume() }); + const branch1a2 = makeBranch({ clip: makeClipVolume() }); + + target.pushBranch(branch1); + // expect clip style to be enabled + expectClipStyle(uniforms, [1,1,1]); + + target.pushBranch(branch1a); + // expect clip style to be disabled + expectClipStyle(uniforms, [0,0,0]); + + target.pushBranch(branch1a1); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [0,0,0]); + + target.pushBranch(branch1a2); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [1,1,1]); + + // after this pop, all branches have been popped + target.popBranch(); + expectClipStyle(uniforms, [1,1,1]); + + /* scenario 2 scene graph: + * branch 1 + * branch 1a: disableClipStyle = true; + * branch 1a1 + * branch 1a1a + * branch 1a2 + * branch 1b + * branch 2 + * branch 2a + * + * clip style should be disabled for branches 1a, 1a1, 1a1a, and 1a2 + */ + + const branch1a1a = makeBranch({ clip: makeClipVolume() }); + const branch1b = makeBranch({ clip: makeClipVolume() }); + const branch2 = makeBranch({ clip: makeClipVolume() }); + const branch2a = makeBranch({ clip: makeClipVolume() }); + + target.pushBranch(branch1); + expectClipStyle(uniforms, [1,1,1]); + + target.pushBranch(branch1a); + expectClipStyle(uniforms, [0,0,0]); + + target.pushBranch(branch1a1); + expectClipStyle(uniforms, [0,0,0]); + + target.pushBranch(branch1a1a); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [0,0,0]); + + target.pushBranch(branch1a2); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [0,0,0]); + + target.popBranch(); + expectClipStyle(uniforms, [1,1,1]); + + target.pushBranch(branch1b); + expectClipStyle(uniforms, [1,1,1]); + + target.popBranch(); + expectClipStyle(uniforms, [1,1,1]); + + target.popBranch(); + expectClipStyle(uniforms, [1,1,1]); + + target.pushBranch(branch2); + expectClipStyle(uniforms, [1,1,1]); + + target.pushBranch(branch2a); + expectClipStyle(uniforms, [1,1,1]); + }); }); diff --git a/core/frontend/src/tile/map/MapTile.ts b/core/frontend/src/tile/map/MapTile.ts index 0c7f2fe47aca..42055ae8451a 100644 --- a/core/frontend/src/tile/map/MapTile.ts +++ b/core/frontend/src/tile/map/MapTile.ts @@ -531,7 +531,7 @@ export class MapTile extends RealityTile { const textures = this.getDrapeTextures(); const { baseColor, baseTransparent, layerClassifiers } = this.mapTree; - const graphic = IModelApp.renderSystem.createRealityMeshGraphic({ realityMesh: geometry, projection: this.getProjection(), tileRectangle: this.rectangle, featureTable: PackedFeatureTable.pack(this.mapLoader.featureTable), tileId: this.contentId, baseColor, baseTransparent, textures, layerClassifiers }, true); + const graphic = IModelApp.renderSystem.createRealityMeshGraphic({ realityMesh: geometry, projection: this.getProjection(), tileRectangle: this.rectangle, featureTable: PackedFeatureTable.pack(this.mapLoader.featureTable), tileId: this.contentId, baseColor, baseTransparent, textures, layerClassifiers, disableClipStyle: true }, true); // If there are no layer classifiers then we can save this graphic for re-use. If layer classifiers exist they are regenerated based on view and we must collate them with the imagery. if (this.imageryIsReady && 0 === this.mapTree.layerClassifiers.size)