Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drape model map layers onto reality models #7637

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions core/common/src/MapLayerSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ export interface CommonMapLayerProps {
* Default: true.
*/
transparentBackground?: boolean;
/** True to drape this layer onto all attached reality data, not the background map. Otherwise, the layer will be draped onto the background map.
* Default: false.
*/
toRealityData?: boolean;
}

/** JSON representation of an [[ImageMapLayerSettings]].
Expand Down Expand Up @@ -216,16 +220,18 @@ export abstract class MapLayerSettings {
public readonly name: string;
public readonly transparency: number;
public readonly transparentBackground: boolean;
public readonly toRealityData: boolean;
public abstract get allSubLayersInvisible(): boolean;
public abstract clone(changedProps: Partial<MapLayerProps>): MapLayerSettings;
public abstract toJSON(): MapLayerProps;

/** @internal */
protected constructor(name: string, visible = true, transparency: number = 0, transparentBackground = true) {
protected constructor(name: string, visible = true, transparency: number = 0, transparentBackground = true, toRealityData = false) {
this.name = name;
this.visible = visible;
this.transparentBackground = transparentBackground;
this.transparency = transparency;
this.toRealityData = toRealityData;
}

/** Create a map layer settings from its JSON representation. */
Expand All @@ -246,6 +252,9 @@ export abstract class MapLayerSettings {
if (this.transparentBackground === false)
props.transparentBackground = this.transparentBackground;

if (this.toRealityData === true)
props.toRealityData = this.toRealityData;

return props;
}

Expand All @@ -255,6 +264,7 @@ export abstract class MapLayerSettings {
name: undefined !== changedProps.name ? changedProps.name : this.name,
visible: undefined !== changedProps.visible ? changedProps.visible : this.visible,
transparency: undefined !== changedProps.transparency ? changedProps.transparency : this.transparency,
toRealityData: undefined !== changedProps.toRealityData ? changedProps.toRealityData : this.toRealityData,
transparentBackground: undefined !== changedProps.transparentBackground ? changedProps.transparentBackground : this.transparentBackground,
};
}
Expand Down Expand Up @@ -303,7 +313,7 @@ export class ImageMapLayerSettings extends MapLayerSettings {
/** @internal */
protected constructor(props: ImageMapLayerProps) {
const transparentBackground = props.transparentBackground ?? true;
super(props.name, props.visible, props.transparency, transparentBackground);
super(props.name, props.visible, props.transparency, transparentBackground, props.toRealityData);

this.formatId = props.formatId;
this.url = props.url;
Expand Down Expand Up @@ -488,15 +498,15 @@ export class ModelMapLayerSettings extends MapLayerSettings {

/** @internal */
protected constructor(modelId: Id64String, name: string, visible = true,
transparency: number = 0, transparentBackground = true) {
super(name, visible, transparency, transparentBackground);
transparency: number = 0, transparentBackground = true, toRealityData = false) {
super(name, visible, transparency, transparentBackground, toRealityData);
this.modelId = modelId;
}

/** Construct from JSON, performing validation and applying default values for undefined fields. */
public static override fromJSON(json: ModelMapLayerProps): ModelMapLayerSettings {
const transparentBackground = (json.transparentBackground === undefined) ? true : json.transparentBackground;
return new this(json.modelId, json.name, json.visible, json.transparency, transparentBackground);
return new this(json.modelId, json.name, json.visible, json.transparency, transparentBackground, json.toRealityData);
}

/** return JSON representation of this MapLayerSettings object */
Expand Down
2 changes: 1 addition & 1 deletion core/frontend-devtools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ This package provides several keyins to control the display of background maps,

* `fdt attach maplayer <name>` - Attach a background map layer from name within the map layer source list. Partial names may be used.
* `fdt attach mapoverlay <name>` - Attach an overlay map layer from name within the map layer source list. Partial names may be used.
* `fdt attach model maplayer <name>` - Attach a model map layer for each unique model of the currently selected elements.
* `fdt attach model maplayer <bgmap|reality> [name]` - Attach a model map layer for each unique model of the currently selected elements to either the background map or attached reality models. If name is specified, use that.

* `fdt set map base <name>` - Set the background base map from name within the map layer source list. Partial names may be used.
* `fdt set map base color <red, green, blue>` - Set map base color by red, green and blue values [0..255].
Expand Down
10 changes: 5 additions & 5 deletions core/frontend-devtools/src/tools/MapLayerTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ class AttachMapLayerBaseTool extends Tool {
* @beta
*/
export class AttachModelMapLayerTool extends Tool {
public static override get minArgs() { return 0; }
public static override get maxArgs() { return 1; }
public static override get minArgs() { return 1; }
public static override get maxArgs() { return 2; }
public static override toolId = "AttachModelMapLayerTool";
constructor(protected _formatId: string) { super(); }

public override async run(nameIn?: string): Promise<boolean> {
public override async run(toRealityData: boolean, nameIn?: string): Promise<boolean> {
const vp = IModelApp.viewManager.selectedView;
if (!vp)
return false;
Expand All @@ -86,14 +86,14 @@ export class AttachModelMapLayerTool extends Tool {
const modelProps = await iModel.models.getProps(modelId);
const modelName = modelProps[0].name ? modelProps[0].name : modelId;
const name = nameIn ? (modelIds.size > 1 ? `${nameIn}: ${modelName}` : nameIn) : modelName;
const settings = ModelMapLayerSettings.fromJSON({ name, modelId });
const settings = ModelMapLayerSettings.fromJSON({ name, modelId, toRealityData });
vp.displayStyle.attachMapLayer({ settings, mapLayerIndex: { isOverlay: false, index: -1 } });
}
return true;
}

public override async parseAndRun(...args: string[]): Promise<boolean> {
return this.run(args[0]);
return this.run("reality" === args[0], args[1]);
}
public async onRestartTool() {
}
Expand Down
5 changes: 5 additions & 0 deletions core/frontend/src/ContextRealityModelState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export class ContextRealityModelState extends ContextRealityModel {
this.rdSourceKey = props.rdSourceKey ? props.rdSourceKey : RealityDataSource.createKeyFromOrbitGtBlobProps(props.orbitGtBlob);
}
const useOrbitGtTileTreeReference = this.rdSourceKey.format === RealityDataFormat.OPC;
const mapSettings = displayStyle.backgroundMapSettings;
const mapImagery = displayStyle.settings.mapImagery;
this._treeRef = (!useOrbitGtTileTreeReference) ?
createRealityTileTreeReference({
iModel,
Expand All @@ -51,6 +53,9 @@ export class ContextRealityModelState extends ContextRealityModel {
classifiers: this.classifiers,
planarClipMask: this.planarClipMaskSettings,
getDisplaySettings: () => this.displaySettings,
mapSettings,
backgroundBase: mapImagery.backgroundBase,
backgroundLayers: mapImagery.backgroundLayers
}) :
createOrbitGtTileTreeReference({
iModel,
Expand Down
14 changes: 12 additions & 2 deletions core/frontend/src/ModelState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { IModelConnection } from "./IModelConnection";
import { RealityDataSource } from "./RealityDataSource";
import { createOrbitGtTileTreeReference, createPrimaryTileTreeReference, createRealityTileTreeReference, TileTreeReference } from "./tile/internal";
import { ViewState } from "./ViewState";
import { SpatialClassifiersState } from "./SpatialClassifiersState";
import { SpatialClassifiersState } from "./SpatialClassifiersState";

/** Represents the front-end state of a [Model]($backend).
* @public
Expand Down Expand Up @@ -120,8 +120,12 @@ export abstract class GeometricModelState extends ModelState implements Geometri
const rdSourceKey = this.jsonProperties.rdSourceKey;
const getDisplaySettings = () => view.displayStyle.settings.getRealityModelDisplaySettings(this.id) ?? RealityModelDisplaySettings.defaults;

const mapSettings = view.displayStyle.backgroundMapSettings;
const mapImagery = view.displayStyle.settings.mapImagery;

if (rdSourceKey) {
const useOrbitGtTileTreeReference = rdSourceKey.format === RealityDataFormat.OPC;

const treeRef = (!useOrbitGtTileTreeReference) ?
createRealityTileTreeReference({
rdSourceKey,
Expand All @@ -131,6 +135,9 @@ export abstract class GeometricModelState extends ModelState implements Geometri
// url: tilesetUrl, // If rdSourceKey is defined, url is not used
classifiers: undefined !== spatialModel ? spatialModel.classifiers : undefined,
getDisplaySettings,
mapSettings,
backgroundBase: mapImagery.backgroundBase,
backgroundLayers: mapImagery.backgroundLayers
}) :
createOrbitGtTileTreeReference({
rdSourceKey,
Expand Down Expand Up @@ -184,7 +191,10 @@ export abstract class GeometricModelState extends ModelState implements Geometri
tilesetToDbTransform: this.jsonProperties.tilesetToDbTransform,
classifiers: undefined !== spatialModel ? spatialModel.classifiers : undefined,
getDisplaySettings,
});
mapSettings,
backgroundBase: mapImagery.backgroundBase,
backgroundLayers: mapImagery.backgroundLayers
});
}

return createPrimaryTileTreeReference(view, this);
Expand Down
54 changes: 52 additions & 2 deletions core/frontend/src/Viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { StandardView, StandardViewId } from "./StandardView";
import { SubCategoriesCache } from "./SubCategoriesCache";
import {
DisclosedTileTreeSet, MapCartoRectangle, MapFeatureInfo, MapFeatureInfoOptions, MapLayerFeatureInfo, MapLayerImageryProvider, MapLayerIndex, MapLayerInfoFromTileTree, MapTiledGraphicsProvider,
MapTileTreeReference, MapTileTreeScaleRangeVisibility, TileBoundingBoxes, TiledGraphicsProvider, TileTreeLoadStatus, TileTreeReference, TileUser,
MapTileTreeReference, MapTileTreeScaleRangeVisibility, RealityModelTileTree, RealityTileTree, TileBoundingBoxes, TiledGraphicsProvider, TileTreeLoadStatus, TileTreeReference, TileUser,
} from "./tile/internal";
import { EventController } from "./tools/EventController";
import { ToolSettings } from "./tools/ToolSettings";
Expand Down Expand Up @@ -425,6 +425,7 @@ export abstract class Viewport implements Disposable, TileUser {

/** Mark the viewport's [[ViewState]] as having changed, so that the next call to [[renderFrame]] will invoke [[setupFromView]] to synchronize with the view.
* This method is not typically invoked directly - the controller is automatically invalidated in response to events such as a call to [[changeView]].
* Additionally, refresh the Reality Tile Tree to reflect changes in the map layer.
*/
public invalidateController(): void {
this._controllerValid = this._analysisFractionValid = false;
Expand Down Expand Up @@ -917,6 +918,50 @@ export abstract class Viewport implements Disposable, TileUser {
return true;
}

/** Refresh the Reality Tile Tree to reflect changes in the map layer. */
private refreshRealityTile(): void {
for (const { supplier, id, owner } of this.iModel.tiles) {
if (owner.tileTree instanceof RealityTileTree) {
this.iModel.tiles.resetTileTreeOwner(id, supplier);
}
}
}

/**
* Helper function that compares the map layer counts of two view states.
* Returns true if the counts are different, otherwise false.
* @param prevView The previous view state.
* @param newView The new view state.
* @internal
*/
private compareMapLayerCounts(prevView: ViewState, newView: ViewState): boolean {
const prevTileTreeRefs = Array.from(prevView.getTileTreeRefs());
const newTileTreeRefs = Array.from(newView.getTileTreeRefs());

const prevRealityRefs = prevTileTreeRefs.filter(
ref => ref instanceof RealityModelTileTree.Reference
);

const newRealityRefs = newTileTreeRefs.filter(
ref => ref instanceof RealityModelTileTree.Reference
);

for (const newRef of newRealityRefs) {
// TODO:
if (newRef.getMapLayerCount() > 1) {
return true; // Always return true if map layer count is greater than 1
}

// If newRef has only one map layer(bg map), check if the previous view has the same map layer count
for (const prevRef of prevRealityRefs) {
if (newRef.getMapLayerCount() !== prevRef.getMapLayerCount()) {
return true;
}
}
}
return false;
}

/** Fully reset a map-layer tile tree; by calling this, the map-layer will to go through initialize process again, and all previously fetched tile will be lost.
* @beta
*/
Expand Down Expand Up @@ -1303,6 +1348,7 @@ export abstract class Viewport implements Disposable, TileUser {
const mapChanged = () => {
this.invalidateController();
this._changeFlags.setDisplayStyle();
this.refreshRealityTile();
};

removals.push(settings.onBackgroundMapChanged.addListener(mapChanged));
Expand Down Expand Up @@ -1783,6 +1829,10 @@ export abstract class Viewport implements Disposable, TileUser {
if (undefined !== prevView && prevView !== view) {
this.onChangeView.raiseEvent(this, prevView);
this._changeFlags.setViewState();

if (this.compareMapLayerCounts(prevView, view)) {
this.refreshRealityTile();
}
}
}

Expand Down Expand Up @@ -3187,7 +3237,7 @@ export class ScreenViewport extends Viewport {
if (undefined !== IModelApp.applicationLogoCard) {
logos.appendChild(IModelApp.applicationLogoCard());
}

logos.appendChild(IModelApp.makeIModelJsLogoCard());
for (const ref of this.getTileTreeRefs()) {
ref.addLogoCards(logos, this);
Expand Down
4 changes: 2 additions & 2 deletions core/frontend/src/render/RealityMeshGraphicParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { MapLayerClassifiers, RenderTerrainGeometry, TerrainTexture } from "./

/** @internal */
export interface RealityMeshGraphicParams {
readonly realityMesh: RenderTerrainGeometry;
readonly realityMesh?: RenderTerrainGeometry;
readonly projection: MapTileProjection;
readonly tileRectangle: MapCartoRectangle;
readonly featureTable: PackedFeatureTable;
readonly featureTable?: PackedFeatureTable;
readonly tileId: string | undefined;
readonly baseColor: ColorDef | undefined;
readonly baseTransparent: boolean;
Expand Down
3 changes: 2 additions & 1 deletion core/frontend/src/render/RealityMeshParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import {
OctEncodedNormal, QPoint2d, QPoint2dBuffer, QPoint2dBufferBuilder, QPoint3d, QPoint3dBuffer, QPoint3dBufferBuilder, RenderTexture,
} from "@itwin/core-common";
import { GltfMeshData } from "../tile/internal";
import { GltfMeshData, RealityTile } from "../tile/internal";
import { MeshPrimitiveType } from "../common/internal/render/MeshPrimitive";

function precondition(condition: boolean, message: string | (() => string)): asserts condition {
Expand Down Expand Up @@ -46,6 +46,7 @@ export interface RealityMeshParams {
featureID?: number; // default 0
/** @alpha unused by terrain meshes */
texture?: RenderTexture;
tile?: RealityTile;
}

/** @public */
Expand Down
Loading