UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

1,020 lines • 48.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module DisplayStyles */ // cspell:ignore greyscale ovrs import { assert, BeEvent, CompressedId64Set, expectDefined, Id64, JsonUtils, MutableCompressedId64Set, OrderedId64Iterable, } from "@itwin/core-bentley"; import { AmbientOcclusion } from "./AmbientOcclusion"; import { AnalysisStyle } from "./AnalysisStyle"; import { BackgroundMapSettings } from "./BackgroundMapSettings"; import { ClipStyle } from "./ClipStyle"; import { ColorDef } from "./ColorDef"; import { HiddenLine } from "./HiddenLine"; import { FeatureAppearance } from "./FeatureSymbology"; import { PlanarClipMaskSettings } from "./PlanarClipMask"; import { SubCategoryOverride } from "./SubCategoryOverride"; import { LightSettings } from "./LightSettings"; import { MapImagerySettings } from "./MapImagerySettings"; import { PlanProjectionSettings } from "./PlanProjectionSettings"; import { Environment } from "./Environment"; import { SolarShadowSettings } from "./SolarShadows"; import { ThematicDisplay, ThematicDisplayMode } from "./ThematicDisplay"; import { ViewFlags } from "./ViewFlags"; import { Cartographic } from "./geometry/Cartographic"; import { IModel } from "./IModel"; import { calculateSolarDirection } from "./SolarCalculate"; import { ContextRealityModels } from "./ContextRealityModel"; import { RealityModelDisplaySettings } from "./RealityModelDisplaySettings"; import { WhiteOnWhiteReversalSettings } from "./WhiteOnWhiteReversalSettings"; import { ContourDisplay } from "./ContourDisplay"; /** Describes the style in which monochrome color is applied by a [[DisplayStyleSettings]]. * @public * @extensions */ export var MonochromeMode; (function (MonochromeMode) { /** The color of the geometry is replaced with the monochrome color. e.g., if monochrome color is white, the geometry will be white. */ MonochromeMode[MonochromeMode["Flat"] = 0] = "Flat"; /** The color of surfaces is computed as normal, then scaled to a shade of the monochrome color based on the surface color's intensity. * For example, if the monochrome color is white, this results in a greyscale effect. * Geometry other than surfaces is treated the same as [[MonochromeMode.Flat]]. */ MonochromeMode[MonochromeMode["Scaled"] = 1] = "Scaled"; })(MonochromeMode || (MonochromeMode = {})); /** DisplayStyleSettings initially persisted its excluded elements as an array of Id64Strings in JSON, and exposed them as a Set<string>. * This becomes problematic when these arrays become very large, in terms of the amount of data and the time required to convert them to a Set. * The Ids are now persisted to JSON as a [[CompressedId64Set]], significantly reducing their size. However, for backwards API compatibility we must * continue to expose [[DisplayStyleSettings.excludedElements]] as a Set<string>. The [[ExcludedElements]] class tries to minimize the impact of that requirement by * maintaining the Ids primarily as a [[MutableCompressedId64Set]], only allocating the Set<string> if a caller actually requests it. * The only operation Set provides more efficiently than MutableCompressedId64Set is checking for the presence of an Id (the `has()` method). * @internal */ class ExcludedElements { _json; _ids; _synchronizing = false; constructor(json) { this._json = json; if (Array.isArray(json.excludedElements)) this._ids = new MutableCompressedId64Set(CompressedId64Set.compressIds(OrderedId64Iterable.sortArray(json.excludedElements))); else this._ids = new MutableCompressedId64Set(json.excludedElements); } reset(ids) { this.synchronize(() => { this._ids.reset((ids && "string" !== typeof ids) ? CompressedId64Set.compressIds(ids) : ids); }); } get ids() { return this._ids.ids; } add(ids) { this.synchronize(() => { for (const id of ids) this._ids.add(id); }); } delete(ids) { this.synchronize(() => { for (const id of ids) this._ids.delete(id); }); } [Symbol.iterator]() { return this._ids[Symbol.iterator](); } /** The JSON must be kept up-to-date at all times. */ synchronize(func) { if (this._synchronizing) return; this._synchronizing = true; try { func(); } finally { this._synchronizing = false; const ids = this._ids.ids; if (0 === ids.length) delete this._json.excludedElements; else this._json.excludedElements = ids; } } } /** An implementation of Map that is based on a JSON array, used for a display styles subcategory overrides, model appearance overrides, * and planar clip masks. Ensures: * - JSON representation kept in sync with changes to map; and * - Events dispatched when map contents change. */ class OverridesMap extends Map { _json; _arrayKey; _event; _idFromProps; _overrideToProps; _overrideFromProps; // This is required for mock framework used by ui libraries, which otherwise try to clone this as a standard Map. get [Symbol.toStringTag]() { return "OverridesMap"; } constructor(_json, _arrayKey, _event, _idFromProps, _overrideToProps, _overrideFromProps) { super(); this._json = _json; this._arrayKey = _arrayKey; this._event = _event; this._idFromProps = _idFromProps; this._overrideToProps = _overrideToProps; this._overrideFromProps = _overrideFromProps; this.populate(); } set(id, override) { this._event.raiseEvent(id, override); super.set(id, override); const index = this.findOrAllocateIndex(id); const array = this._array; assert(undefined !== array); array[index] = this._overrideToProps(override, id); return this; } delete(id) { this._event.raiseEvent(id, undefined); if (!super.delete(id)) return false; const index = this.findExistingIndex(id); if (undefined !== index) { assert(undefined !== this._array); this._array.splice(index, 1); } return true; } clear() { for (const id of this.keys()) this.delete(id); this._json[this._arrayKey] = undefined; } populate() { super.clear(); const ovrs = this._array; if (!ovrs) return; for (const props of ovrs) { const id = this._idFromProps(props); if (undefined !== id && Id64.isValidId64(id)) { const ovr = this._overrideFromProps(props); if (ovr) super.set(id, ovr); } } } get _array() { return JsonUtils.asArray(this._json[this._arrayKey]); } findOrAllocateIndex(id) { const index = this.findExistingIndex(id); if (undefined !== index) return index; let ovrs = this._array; if (!ovrs) ovrs = this._json[this._arrayKey] = []; return ovrs.length; } findExistingIndex(id) { const ovrs = this._array; if (!ovrs) return undefined; for (let i = 0; i < ovrs.length; i++) if (this._idFromProps(ovrs[i]) === id) return i; return undefined; } } /** Provides access to the settings defined by a [[DisplayStyle]] or [[DisplayStyleState]], and ensures that * the style's JSON properties are kept in sync. * @see [[DisplayStyleSettingsProps]] for the JSON representation of these settings. * @public */ export class DisplayStyleSettings { _json; _viewFlags; _background; _monochrome; _monochromeMode; _subCategoryOverrides; _modelAppearanceOverrides; _realityModelDisplaySettings; _planarClipMasks; _excludedElements; _backgroundMap; _mapImagery; _analysisStyle; _clipStyle; _contextRealityModels; _whiteOnWhiteReversal; /** Returns true if this is a [[DisplayStyle3dSettings]]. */ is3d() { return false; } /** Planar clip masks to be applied to persistent reality models (@see [SpatialModelState.isRealityModel]($frontend). * The key for each entry is the Id of the model to which the mask settings apply. */ get planarClipMasks() { return this._planarClipMasks; } /** Reality models to be displayed in the view. */ get contextRealityModels() { return this._contextRealityModels; } /** Event raised by [[applyOverrides]] just before the overrides are applied. */ onApplyOverrides = new BeEvent(); /** Event raised by [[applyOverrides]] after the overrides are applied. */ onOverridesApplied = new BeEvent(); /** Event raised just prior to assignment to the [[viewFlags]] property. */ onViewFlagsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[backgroundColor]] property. */ onBackgroundColorChanged = new BeEvent(); /** Event raised just prior to assignment to the [[monochromeColor]] property. */ onMonochromeColorChanged = new BeEvent(); /** Event raised just prior to assignment to the [[monochromeMode]] property. */ onMonochromeModeChanged = new BeEvent(); /** Event raised just prior to assignment to the [[backgroundMap]] property. */ onBackgroundMapChanged = new BeEvent(); /** Event raised just prior to assignment to the [[mapImagery]] property. * @beta */ onMapImageryChanged = new BeEvent(); /** Event raised just prior to assignment to the `scheduleScriptProps` property. * @see [[onRenderTimelineChanged]] to be notified when the [[renderTimeline]] property from which a script can be obtained is changed. */ onScheduleScriptPropsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[renderTimeline]] property. */ onRenderTimelineChanged = new BeEvent(); /** Event raised just prior to assignment to the [[timePoint]] property. */ onTimePointChanged = new BeEvent(); /** Event raised just prior to assignment to the [[analysisStyle]] property. */ onAnalysisStyleChanged = new BeEvent(); /** Event raised just prior to assignment to the [[analysisFraction]] property. */ onAnalysisFractionChanged = new BeEvent(); /** Event raised when the contents of [[excludedElementIds]] changes. */ onExcludedElementsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[clipStyle]] property. */ onClipStyleChanged = new BeEvent(); /** Event raised when the [[SubCategoryOverride]]s change. */ onSubCategoryOverridesChanged = new BeEvent(); /** Event raised just before changing the appearance override for a model. */ onModelAppearanceOverrideChanged = new BeEvent(); /** Event raised just before [[setRealityModelDisplaySettings]] changes the display settings for a reality model. * @beta */ onRealityModelDisplaySettingsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.thematic]] property. */ onThematicChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.contours]] property. */ onContoursChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.hiddenLineSettings]] property. */ onHiddenLineSettingsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.ambientOcclusionSettings]] property. */ onAmbientOcclusionSettingsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.solarShadows]] property. */ onSolarShadowsChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.environment]] property. */ onEnvironmentChanged = new BeEvent(); /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.lights]] property. */ onLightsChanged = new BeEvent(); /** Event raised just before changing the plan projection settings for a model. */ onPlanProjectionSettingsChanged = new BeEvent(); /** Event raised just before adding or removing an entry from [[planarClipMasks]]. */ onPlanarClipMaskChanged = new BeEvent(); /** Event raised just prior to assignment to the [[whiteOnWhiteReversal]] property. */ onWhiteOnWhiteReversalChanged = new BeEvent(); /** Construct a new DisplayStyleSettings from an [[ElementProps.jsonProperties]]. * @param jsonProperties An object with an optional `styles` property containing a display style's settings. * @param options Options for customizing the display style settings. * @note When the `DisplayStyleSetting`'s properties are modified by public setters, the `jsonProperties`'s `styles` object will be updated to reflect the change. * @note If `jsonProperties` contains no `styles` member, one will be added as an empty object. * @note Generally there is no reason to create an object of this type directly; a [[DisplayStyle]] or [[DisplayStyleState]] constructs one as part of its own construction. */ constructor(jsonProperties, options) { if (undefined === jsonProperties.styles) jsonProperties.styles = {}; this._json = jsonProperties.styles; this._viewFlags = ViewFlags.fromJSON(this._json.viewflags); this._background = ColorDef.fromJSON(this._json.backgroundColor); this._monochrome = undefined !== this._json.monochromeColor ? ColorDef.fromJSON(this._json.monochromeColor) : ColorDef.white; this._monochromeMode = MonochromeMode.Flat === this._json.monochromeMode ? MonochromeMode.Flat : MonochromeMode.Scaled; this._backgroundMap = BackgroundMapSettings.fromPersistentJSON(this._json.backgroundMap); this._mapImagery = MapImagerySettings.createFromJSON(this._json.mapImagery, this._json.backgroundMap); // Ensure that if we used the deprecated imagery properties from this._backgroundMap to set up the base layer of this._mapImagery, // we update our JSON to include that base layer. this._json.mapImagery = this._mapImagery.toJSON(); this._excludedElements = new ExcludedElements(this._json); if (this._json.analysisStyle) this._analysisStyle = AnalysisStyle.fromJSON(this._json.analysisStyle); this._whiteOnWhiteReversal = WhiteOnWhiteReversalSettings.fromJSON(this._json.whiteOnWhiteReversal); this._clipStyle = ClipStyle.fromJSON(this._json.clipStyle); this._subCategoryOverrides = new OverridesMap(this._json, "subCategoryOvr", this.onSubCategoryOverridesChanged, (props) => props.subCategory, (ovr, subCategory) => { return { ...ovr.toJSON(), subCategory }; }, (props) => { const ovr = SubCategoryOverride.fromJSON(props); return ovr.anyOverridden ? ovr : undefined; }); this._modelAppearanceOverrides = new OverridesMap(this._json, "modelOvr", this.onModelAppearanceOverrideChanged, (props) => props.modelId, (ovr, modelId) => { return { ...ovr.toJSON(), modelId }; }, (props) => { const app = FeatureAppearance.fromJSON(props); return app.anyOverridden ? app : undefined; }); this._realityModelDisplaySettings = new OverridesMap(this._json, "realityModelDisplay", this.onRealityModelDisplaySettingsChanged, (props) => props.modelId, (settings, modelId) => { return { ...settings.toJSON(), modelId }; }, (props) => RealityModelDisplaySettings.fromJSON(props)); this._planarClipMasks = new OverridesMap(this._json, "planarClipOvr", this.onPlanarClipMaskChanged, (props) => props.modelId, (ovr, modelId) => { return { ...ovr.toJSON(), modelId }; }, (props) => { const settings = PlanarClipMaskSettings.fromJSON(props); return settings.isValid ? settings : undefined; }); this._contextRealityModels = new ContextRealityModels({ container: this._json, createContextRealityModel: options?.createContextRealityModel, deferPopulating: options?.deferContextRealityModels, }); } /** Flags controlling various aspects of the display style. */ get viewFlags() { return this._viewFlags; } set viewFlags(flags) { if (this.viewFlags.equals(flags)) return; this.onViewFlagsChanged.raiseEvent(flags); this._viewFlags = flags; this._json.viewflags = flags.toJSON(); } /** The color displayed in the view background - by default, [[ColorDef.black]]. */ get backgroundColor() { return this._background; } set backgroundColor(color) { if (this.backgroundColor.equals(color)) return; this.onBackgroundColorChanged.raiseEvent(color); this._background = color; this._json.backgroundColor = color.toJSON(); } /** The color used to draw geometry when [[ViewFlags.monochrome]] is enabled - by default, [[ColorDef.white]]. * The monochrome color is applied to all surfaces and linear geometry, but only applied to the **edges** of surfaces in [[RenderMode.Wireframe]]. * @see [[monochromeMode]] to control how the color is applied. */ get monochromeColor() { return this._monochrome; } set monochromeColor(color) { if (this.monochromeColor.equals(color)) return; this.onMonochromeColorChanged.raiseEvent(color); this._monochrome = color; this._json.monochromeColor = color.toJSON(); } /** The style in which [[monochromeColor]] is applied when [[ViewFlags.monochrome]] is enabled - by default, [[MonochromeMode.Scaled]]. */ get monochromeMode() { return this._monochromeMode; } set monochromeMode(mode) { if (this.monochromeMode === mode) return; this.onMonochromeModeChanged.raiseEvent(mode); this._monochromeMode = mode; this._json.monochromeMode = mode; } /** Settings controlling display of the background map within views of geolocated models. */ get backgroundMap() { return this._backgroundMap; } set backgroundMap(map) { if (!this.backgroundMap.equals(map)) { this.onBackgroundMapChanged.raiseEvent(map); this._backgroundMap = map; // it's an immutable type. this._json.backgroundMap = map.toPersistentJSON(); } } /** Settings defining the map imagery layers to be displayed within the view. * @beta */ get mapImagery() { return this._mapImagery; } set mapImagery(mapImagery) { this.onMapImageryChanged.raiseEvent(mapImagery); this._mapImagery = mapImagery; this._json.mapImagery = this._mapImagery.toJSON(); } /** @internal * Handles keeping the map imagery layers in synch after changes have been made (used internally only by front end) */ synchMapImagery() { this.onMapImageryChanged.raiseEvent(this._mapImagery); this._json.mapImagery = this._mapImagery.toJSON(); } /** The Id of a [RenderTimeline]($backend) element containing a [[RenderSchedule.Script]] used to animate the view. * If [[scheduleScriptProps]] is defined, it takes precedence over the script supplied by the RenderTimeline. * @note If this [[DisplayStyleSettings]] is associated with a [DisplayStyleState]($frontend), assigning to [[renderTimeline]] will enqueue asynchronous loading of * the script from the [RenderTimeline]($backend) element; for more readable code, prefer instead to `await` [DisplayStyleState.changeRenderTimeline]($frontend). * @see [[onRenderTimelineChanged]] to be notified of changes to this property. */ get renderTimeline() { return this._json.renderTimeline; } set renderTimeline(id) { if (id !== this.renderTimeline) { this.onRenderTimelineChanged.raiseEvent(id); this._json.renderTimeline = id; } } /** JSON representation of a [[RenderSchedule.Script]] embedded in the display style describing how to animate the contents of the view over time. * This script, if present, takes precedence over a script supplied by [[renderTimeline]]. * @see [[onScheduleScriptPropsChanged]] to be notified when this property changes. * @see [DisplayStyleState.scheduleScript]($frontend) to change the [[RenderSchedule.Script]] object directly rather than via JSON. */ get scheduleScriptProps() { return this._json.scheduleScript; } set scheduleScriptProps(props) { this.onScheduleScriptPropsChanged.raiseEvent(props); this._json.scheduleScript = props; } /** The point in time currently reflected by the view, expressed in seconds in the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time). * This identifies a point on the timeline of the style's [[RenderSchedule.Script]], if any; it may also affect display of four-dimensional reality models. * @see [[onTimePointChanged]] to be notified of changes to this property. */ get timePoint() { return this._json.timePoint; } set timePoint(timePoint) { if (timePoint !== this.timePoint) { this.onTimePointChanged.raiseEvent(timePoint); this._json.timePoint = timePoint; } } /** Settings controlling the display of analytical models. * @see [[analysisFraction]] to control playback of the animation. */ get analysisStyle() { return this._analysisStyle; } set analysisStyle(style) { if (style === this.analysisStyle) return; this.onAnalysisStyleChanged.raiseEvent(style); this._analysisStyle = style; if (style) this._json.analysisStyle = style.toJSON(); else delete this._json.analysisStyle; } /** A floating point value in [0..1] indicating the current point in animation of the [[analysisStyle]], where 0 corresponds to the beginning of * the animation and 1 to the end. Default: 0.0. */ get analysisFraction() { const fraction = this._json.analysisFraction ?? 0; return Math.max(0, Math.min(1, fraction)); } set analysisFraction(fraction) { if (this.analysisFraction === fraction) return; this.onAnalysisFractionChanged.raiseEvent(fraction); this._json.analysisFraction = Math.max(0, Math.min(1, fraction)); } /** Settings controlling how white-on-white reversal is applied when [[ViewFlags.whiteOnWhiteReversal]] is enabled. */ get whiteOnWhiteReversal() { return this._whiteOnWhiteReversal; } set whiteOnWhiteReversal(settings) { if (settings.equals(this.whiteOnWhiteReversal)) return; this.onWhiteOnWhiteReversalChanged.raiseEvent(settings); this._whiteOnWhiteReversal = settings; const json = settings.toJSON(); if (json) this._json.whiteOnWhiteReversal = json; else delete this._json.whiteOnWhiteReversal; } /** Customize the way geometry belonging to a [[SubCategory]] is drawn by this display style. * @param id The Id of the SubCategory whose appearance is to be overridden. * @param ovr The overrides to apply to the [[SubCategoryAppearance]]. * @see [[dropSubCategoryOverride]] */ overrideSubCategory(id, ovr) { this.subCategoryOverrides.set(id, ovr); } /** Remove any [[SubCategoryOverride]] applied to a [[SubCategoryAppearance]] by this style. * @param id The Id of the [[SubCategory]]. * @see [[overrideSubCategory]] */ dropSubCategoryOverride(id) { this.subCategoryOverrides.delete(id); } /** The overrides applied by this style. */ get subCategoryOverrides() { return this._subCategoryOverrides; } /** Obtain the override applied to a [[SubCategoryAppearance]] by this style. * @param id The Id of the [[SubCategory]]. * @returns The corresponding SubCategoryOverride, or undefined if the SubCategory's appearance is not overridden. * @see [[overrideSubCategory]] */ getSubCategoryOverride(id) { return this.subCategoryOverrides.get(id); } /** Returns true if an [[SubCategoryOverride]]s are defined by this style. */ get hasSubCategoryOverride() { return this.subCategoryOverrides.size > 0; } /** Customize the way a [Model]($backend) is drawn by this display style. * @param modelId The Id of the [Model]($backend) whose appearance is to be overridden. * @param ovr The overrides to apply to the [Model]($backend) . * @see [[dropModelAppearanceOverride]] */ overrideModelAppearance(modelId, ovr) { this.modelAppearanceOverrides.set(modelId, ovr); } /** Remove any appearance overrides applied to a [Model]($backend) by this style. * @param modelId The Id of the [Model]($backend) . * @param ovr The overrides to apply to the [Model]($backend) . * @see [[overrideModelAppearance]] */ dropModelAppearanceOverride(id) { this.modelAppearanceOverrides.delete(id); } /** The overrides applied by this style. */ get modelAppearanceOverrides() { return this._modelAppearanceOverrides; } /** Obtain the override applied to a [Model]($backend) by this style. * @param id The Id of the [Model]($backend). * @returns The corresponding FeatureAppearance, or undefined if the Model's appearance is not overridden. * @see [[overrideModelAppearance]] */ getModelAppearanceOverride(id) { return this.modelAppearanceOverrides.get(id); } /** Returns true if model appearance overrides are defined by this style. */ get hasModelAppearanceOverride() { return this.modelAppearanceOverrides.size > 0; } /** Get any settings that override how the reality model with the specified Id is displayed. * @param modelId The Id of the [Model]($backend). * @returns the display settings, or `undefined` if no settings have been associated with `modelId`. * @see [[setRealityModelDisplaySettings]] to change the settings. * @beta */ getRealityModelDisplaySettings(modelId) { return this._realityModelDisplaySettings.get(modelId); } /** Change the settings that control how the reality model with the specified Id is displayed. * @param modelId The Id of the [Model]($backend) to which the settings apply. * @param settings The settings to apply to the model, or `undefined` to clear any previous settings for that model. * @beta */ setRealityModelDisplaySettings(modelId, settings) { if (settings) this._realityModelDisplaySettings.set(modelId, settings); else this._realityModelDisplaySettings.delete(modelId); } /** The set of elements that will not be drawn by this display style. * @returns An iterable over the elements' Ids. */ get excludedElementIds() { return this._excludedElements; } /** @internal */ get compressedExcludedElementIds() { return this._excludedElements.ids; } /** Add one or more elements to the set of elements not to be displayed. * @param id The Ids of the element(s) to be excluded. */ addExcludedElements(id) { this._excludedElements.add("string" === typeof id ? [id] : id); this.onExcludedElementsChanged.raiseEvent(); } /** Remove an element from the set of elements not to be displayed. */ dropExcludedElement(id) { this._excludedElements.delete([id]); this.onExcludedElementsChanged.raiseEvent(); } /** Remove one or more elements from the set of elements not to be displayed. * @param id The Ids of the element(s) to be removed from the set of excluded elements. */ dropExcludedElements(id) { this._excludedElements.delete("string" === typeof id ? [id] : id); this.onExcludedElementsChanged.raiseEvent(); } /** Remove all elements from the set of elements not to be displayed. */ clearExcludedElements() { this._excludedElements.reset(undefined); this.onExcludedElementsChanged.raiseEvent(); } /** The style applied to the view's [ClipVector]($core-geometry). */ get clipStyle() { return this._clipStyle; } set clipStyle(style) { this.onClipStyleChanged.raiseEvent(style); this._clipStyle = style; if (style.matchesDefaults) delete this._json.clipStyle; else this._json.clipStyle = style.toJSON(); } /** Convert these settings to their JSON representation. */ toJSON() { return this._json; } /** Serialize a subset of these settings to JSON, such that they can be applied to another DisplayStyleSettings to selectively override those settings. * @param options Specifies which settings should be serialized. By default, settings that are specific to an iModel (e.g., subcategory overrides) or iTwin (e.g., context reality models) * are omitted, as are drawing aids (e.g., ACS triad and grid). * @returns a JSON representation of the selected settings suitable for passing to [[applyOverrides]]. * @see [[applyOverrides]] to apply the overrides to another DisplayStyleSettings.. */ toOverrides(options) { if (options?.includeAll) { return { ...this.toJSON(), viewflags: this.viewFlags.toFullyDefinedJSON(), }; } const viewflags = this.viewFlags.toFullyDefinedJSON(); const props = { viewflags, backgroundColor: this.backgroundColor.toJSON(), monochromeColor: this.monochromeColor.toJSON(), monochromeMode: this.monochromeMode, whiteOnWhiteReversal: this.whiteOnWhiteReversal.toJSON() ?? { ignoreBackgroundColor: false }, }; if (options?.includeBackgroundMap) { props.backgroundMap = this.backgroundMap.toPersistentJSON(); props.mapImagery = this.mapImagery.toJSON(); } else { delete viewflags.backgroundMap; } if (!options?.includeDrawingAids) { delete viewflags.acs; delete viewflags.grid; } if (options?.includeITwinSpecific || options?.includeIModelSpecific) { props.timePoint = this.timePoint; if (this._json.contextRealityModels) { props.contextRealityModels = this._json.contextRealityModels; if (!options?.includeIModelSpecific) for (const model of this._json.contextRealityModels) delete model.classifiers; } } if (options?.includeIModelSpecific) { if (this.analysisStyle) { props.analysisStyle = this.analysisStyle.toJSON(); props.analysisFraction = this.analysisFraction; } if (this.scheduleScriptProps) props.scheduleScript = [...this.scheduleScriptProps]; if (this.renderTimeline) props.renderTimeline = this.renderTimeline; props.subCategoryOvr = this._json.subCategoryOvr ? [...this._json.subCategoryOvr] : []; props.modelOvr = this._json.modelOvr ? [...this._json.modelOvr] : []; props.excludedElements = this._excludedElements.ids; } return props; } /** Selectively override some of these settings. Any field that is explicitly defined by the input will be overridden in these settings; any fields left undefined in the input * will retain their current values in these settings. The input's [[ViewFlags]] are applied individually - only those flags that are explicitly defined will be overridden. * For example, the following overrides will set the render mode to "smooth", change the background color to white, turn shadows off, and leave all other settings intact: * ```ts * { * viewflags: { * renderMode: RenderMode.SmoothShade, * shadows: false, * }, * backgroundColor: ColorByName.white, * } * ``` * @see [[toOverrides]] to produce overrides from an existing DisplayStyleSettings. */ applyOverrides(overrides) { this._applyOverrides(overrides); this.onOverridesApplied.raiseEvent(overrides); } /** @internal */ _applyOverrides(overrides) { this.onApplyOverrides.raiseEvent(overrides); if (overrides.viewflags) { this.viewFlags = ViewFlags.fromJSON({ ...this.viewFlags.toJSON(), ...overrides.viewflags, }); } if (undefined !== overrides.backgroundColor) this.backgroundColor = ColorDef.fromJSON(overrides.backgroundColor); if (undefined !== overrides.monochromeColor) this.monochromeColor = ColorDef.fromJSON(overrides.monochromeColor); if (undefined !== overrides.monochromeMode) this.monochromeMode = overrides.monochromeMode; if (overrides.backgroundMap) this.backgroundMap = BackgroundMapSettings.fromPersistentJSON(overrides.backgroundMap); if (overrides.mapImagery) this.mapImagery = MapImagerySettings.createFromJSON(overrides.mapImagery, this.backgroundMap.toPersistentJSON()); if (undefined !== overrides.timePoint) this.timePoint = overrides.timePoint; if (overrides.contextRealityModels) { this.contextRealityModels.clear(); for (const props of overrides.contextRealityModels) this.contextRealityModels.add(props); } if (overrides.analysisStyle) this.analysisStyle = AnalysisStyle.fromJSON(overrides.analysisStyle); if (overrides.whiteOnWhiteReversal) this.whiteOnWhiteReversal = WhiteOnWhiteReversalSettings.fromJSON(overrides.whiteOnWhiteReversal); if (undefined !== overrides.analysisFraction) this.analysisFraction = overrides.analysisFraction; if (overrides.scheduleScript) this.scheduleScriptProps = [...overrides.scheduleScript]; if (overrides.renderTimeline) this.renderTimeline = overrides.renderTimeline; if (overrides.subCategoryOvr) { this._json.subCategoryOvr = [...overrides.subCategoryOvr]; this._subCategoryOverrides.populate(); } if (overrides.modelOvr) { this._json.modelOvr = [...overrides.modelOvr]; this._modelAppearanceOverrides.populate(); } if (overrides.realityModelDisplay) { this._json.realityModelDisplay = [...overrides.realityModelDisplay]; this._realityModelDisplaySettings.populate(); } if (overrides.excludedElements) this._excludedElements.reset("string" === typeof overrides.excludedElements ? overrides.excludedElements : [...overrides.excludedElements]); this.onOverridesApplied.raiseEvent(overrides); } } /** Provides access to the settings defined by a [[DisplayStyle3d]] or [[DisplayStyle3dState]], and ensures that * the style's JSON properties are kept in sync. * @public */ export class DisplayStyle3dSettings extends DisplayStyleSettings { _thematic; _contours; _hline; _ao; _solarShadows; _lights; _environment; _planProjections; get _json3d() { return this._json; } is3d() { return true; } constructor(jsonProperties, options) { super(jsonProperties, options); this._thematic = ThematicDisplay.fromJSON(this._json3d.thematic); this._contours = ContourDisplay.fromJSON(this._json3d.contours); this._hline = HiddenLine.Settings.fromJSON(this._json3d.hline); this._ao = AmbientOcclusion.Settings.fromJSON(this._json3d.ao); this._solarShadows = SolarShadowSettings.fromJSON(this._json3d.solarShadows); this._environment = Environment.fromJSON(this._json3d.environment); // Very long ago we used to stick MicroStation's light settings into json.sceneLights. Later we started adding the sunDir. // We don't want any of MicroStation's settings. We do want to preserve the sunDir if present. if (this._json3d.lights) { this._lights = LightSettings.fromJSON(this._json3d.lights); } else { const sunDir = this._json3d.sceneLights?.sunDir; this._lights = LightSettings.fromJSON(sunDir ? { solar: { direction: sunDir } } : undefined); } this.populatePlanProjectionsFromJSON(); } populatePlanProjectionsFromJSON() { this._planProjections = undefined; const projections = this._json3d.planProjections; if (undefined !== projections) { for (const key of Object.keys(projections)) { const id = Id64.fromJSON(key); if (!Id64.isValidId64(id)) { delete projections[key]; continue; } const settings = PlanProjectionSettings.fromJSON(projections[key]); if (undefined === settings) { delete projections[key]; continue; } if (undefined === this._planProjections) this._planProjections = new Map(); this._planProjections.set(id, settings); } } } /** Convert these settings to their JSON representation. */ toJSON() { return this._json3d; } /** See [[DisplayStyleSettings.toOverrides]]. */ toOverrides(options) { const props = super.toOverrides(options); if (options?.includeAll) return props; assert(undefined !== props.viewflags); props.environment = this.environment.toJSON(); props.hline = this.hiddenLineSettings.toJSON(); props.ao = this.ambientOcclusionSettings.toJSON(); props.solarShadows = this.solarShadows.toJSON(); props.lights = this.lights.toJSON(); if (options?.includeIModelSpecific) { props.thematic = this.thematic.toJSON(); if (this._json3d.planProjections) props.planProjections = { ...this._json3d.planProjections }; } else if (ThematicDisplayMode.InverseDistanceWeightedSensors !== this.thematic.displayMode) { props.thematic = { ...this.thematic.toJSON(), sensorSettings: undefined, }; if (ThematicDisplayMode.Height === props.thematic.displayMode) { // DisplayStyle3dState will compute range based on project extents. props.thematic.range = undefined; } } props.contours = this.contours.toJSON(); return props; } /** See [[DisplayStyleSettings.applyOverrides]]. */ applyOverrides(overrides) { super._applyOverrides(overrides); if (overrides.environment) this.environment = Environment.fromJSON(overrides.environment); if (overrides.hline) this.hiddenLineSettings = HiddenLine.Settings.fromJSON(overrides.hline); if (overrides.ao) this.ambientOcclusionSettings = AmbientOcclusion.Settings.fromJSON(overrides.ao); if (overrides.solarShadows) this.solarShadows = SolarShadowSettings.fromJSON(overrides.solarShadows); if (overrides.lights) this.lights = LightSettings.fromJSON(overrides.lights); if (overrides.planProjections) { this._json3d.planProjections = { ...overrides.planProjections }; this.populatePlanProjectionsFromJSON(); } if (overrides.thematic) this.thematic = ThematicDisplay.fromJSON(overrides.thematic); if (overrides.contours) this.contours = ContourDisplay.fromJSON(overrides.contours); this.onOverridesApplied.raiseEvent(overrides); } /** The settings that control thematic display. */ get thematic() { return this._thematic; } set thematic(thematic) { if (thematic.equals(this.thematic)) return; this.onThematicChanged.raiseEvent(thematic); this._thematic = thematic; this._json3d.thematic = thematic.toJSON(); } /** The settings that control contour display. */ get contours() { return this._contours; } set contours(contours) { if (contours.equals(this.contours)) return; this.onContoursChanged.raiseEvent(contours); this._contours = contours; this._json3d.contours = contours.toJSON(); } /** The settings that control how visible and hidden edges are displayed. */ get hiddenLineSettings() { return this._hline; } set hiddenLineSettings(hline) { if (hline.equals(this.hiddenLineSettings)) return; this.onHiddenLineSettingsChanged.raiseEvent(hline); this._hline = hline; this._json3d.hline = hline.toJSON(); } /** The settings that control how ambient occlusion is displayed. */ get ambientOcclusionSettings() { return this._ao; } set ambientOcclusionSettings(ao) { this.onAmbientOcclusionSettingsChanged.raiseEvent(ao); this._ao = ao; this._json3d.ao = ao.toJSON(); } /** The settings that control how solar shadows are displayed. */ get solarShadows() { return this._solarShadows; } set solarShadows(solarShadows) { if (solarShadows.equals(this.solarShadows)) return; this.onSolarShadowsChanged.raiseEvent(solarShadows); this._solarShadows = solarShadows; const json = solarShadows.toJSON(); if (!json) delete this._json3d.solarShadows; else this._json3d.solarShadows = json; } /** Controls the display of a [[SkyBox]], [[GroundPlane]], and [[Atmosphere]]. * @public */ get environment() { return this._environment; } set environment(environment) { if (environment !== this.environment) { this.onEnvironmentChanged.raiseEvent(environment); this._environment = environment; this._json3d.environment = environment.toJSON(); } } /** Toggle display of the [[environment]]'s [[SkyBox]]. * @param display Whether to display the skybox, or `undefined` to toggle the current display. */ toggleSkyBox(display) { display = display ?? this.environment.displaySky; if (display !== this.environment.displaySky) this.environment = this.environment.withDisplay({ sky: display }); } /** Toggle display of the [[environment]]'s [[GroundPlane]]. * @param display Whether to display the ground plane, or `undefined` to toggle the current display. */ toggleGroundPlane(display) { display = display ?? this.environment.displayGround; if (display !== this.environment.displayGround) this.environment = this.environment.withDisplay({ ground: display }); } /** Toggle display of the [[environment]]'s [[Atmosphere]]. * @beta * @param display Whether to display the atmosphere, or `undefined` to toggle the current display. */ toggleAtmosphere(display) { display = display ?? this.environment.displayAtmosphere; if (display !== this.environment.displayAtmosphere) this.environment = this.environment.withDisplay({ atmosphere: display }); } get lights() { return this._lights; } set lights(lights) { if (this.lights.equals(lights)) return; this.onLightsChanged.raiseEvent(lights); this._lights = lights; this._json3d.lights = lights.toJSON(); } /** Adjust the solar light direction based on a date and time at a geographic location. * This replaces `this.lights` with a copy that records the time point and the computed direction. * @param timePoint The time in UNIX milliseconds. * @param location The geographic location; or an iModel, in which case the iModel's [[EcefLocation]] is used. * @see [[sunTime]] to get the current sun time. * @see [[clearSunTime]] to clear the time point. * @note If `location` is an iModel lacking an EcefLocation, a location in Exton, Pennsylvania will be used to compute the light direction instead. */ setSunTime(timePoint, location) { let cartoCenter; if (location instanceof IModel) { if (location.ecefLocation) cartoCenter = Cartographic.fromEcef(location.ecefLocation.origin); if (!cartoCenter) cartoCenter = Cartographic.fromDegrees({ longitude: -75.17035, latitude: 39.954927, height: 0.0 }); } else { cartoCenter = location; } const direction = calculateSolarDirection(new Date(timePoint), cartoCenter); this.lights = this.lights.clone({ solar: { direction, timePoint } }); } /** Clear the solar time point stored in `this.lights.solarLight`. * @note This does not affect the solar light direction. * @see [[sunTime]] to get the current sun time. * @see [[setSunTime]] to set the time point and the solar light direction derived from it. */ clearSunTime() { if (this.lights.solar.timePoint === undefined) return; const solar = this.lights.solar.toJSON() ?? {}; solar.timePoint = undefined; this.lights = this.lights.clone({ solar }); } /** The time point from which the solar light direction was derived, in UNIX milliseconds. * @see [[setSunTime]] to change the time point and solar direction. * @see [[clearSunTime]] to reset the time point to `undefined`. */ get sunTime() { return this.lights.solar.timePoint; } /** Get the plan projection settings associated with the specified model, if defined. */ getPlanProjectionSettings(modelId) { return undefined !== this._planProjections ? this._planProjections.get(modelId) : undefined; } /** Set or clear the plan projection settings associated with the specified model. */ setPlanProjectionSettings(modelId, settings) { this.onPlanProjectionSettingsChanged.raiseEvent(modelId, settings); if (undefined === settings) { if (undefined !== this._planProjections) { assert(undefined !== this._json3d.planProjections); this._planProjections.delete(modelId); delete this._json3d.planProjections[modelId]; if (0 === this._planProjections.size) { this._planProjections = undefined; delete this._json3d.planProjections; } } return; } if (undefined === this._planProjections) { this._planProjections = new Map(); this._json3d.planProjections = {}; } this._planProjections.set(modelId, settings); expectDefined(this._json3d.planProjections)[modelId] = settings.toJSON(); } /** An iterator over all of the defined plan projection settings. The iterator includes the Id of the model associated with each settings object. */ get planProjectionSettings() { return undefined !== this._planProjections ? this._planProjections.entries() : undefined; } } //# sourceMappingURL=DisplayStyleSettings.js.map