@itwin/core-common
Version:
iTwin.js components common to frontend and backend
694 lines • 36.9 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Rendering
*/
import { assert, Id64 } from "@itwin/core-bentley";
import { BatchType } from "./FeatureTable";
import { GeometryClass } from "./GeometryParams";
import { RgbColor } from "./RgbColor";
function copyIdSetToUint32Set(dst, src) {
dst.clear();
if (typeof src === "string") {
dst.addId(src);
}
else {
for (const id of src)
dst.addId(id);
}
}
function propsMatchDefaults(props) {
return !props.rgb && !props.lineRgb
&& undefined === props.weight && undefined === props.linePixels
&& undefined === props.transparency && undefined === props.lineTransparency
&& !props.ignoresMaterial && !props.nonLocatable && !props.emphasized;
}
function equalRgb(a, b) {
if (a === b) {
return true;
}
else if (!a || !b) {
return false;
}
else {
return a.equals(b);
}
}
function equalLineRgb(a, b) {
if (a === b) {
return true;
}
else if (a instanceof RgbColor && b instanceof RgbColor) {
return equalRgb(a, b);
}
else {
return false;
}
}
function equalTransparency(a, b) {
if (a === b) {
return true;
}
else if (undefined === a || undefined === b) {
return false;
}
else {
return Math.floor(a * 0xff) === Math.floor(b * 0xff);
}
}
function equalLineTransparency(a, b) {
if (a === b) {
return true;
}
else if (typeof a === "number" && typeof b === "number") {
return equalTransparency(a, b);
}
else {
assert(a === undefined || a === false);
assert(b === undefined || b === false);
return false;
}
}
function transparencyFromJSON(transp) {
if (undefined === transp) {
return undefined;
}
transp = Math.max(0, Math.min(transp, 1));
// Fix up rounding errors...
const smallDelta = 0.0001;
if (1.0 - transp < smallDelta) {
transp = 1.0;
}
else if (transp < smallDelta) {
transp = 0.0;
}
return transp;
}
/** Defines overrides for selected aspects of a [[Feature]]'s symbology.
* Any member defined in the appearance overrides that aspect of symbology for all [[Feature]]s to which the appearance is applied.
*
* The [[rgb]] and [[transparency]] overrides, if defined, apply to all geometry by default.
* However, the color and transparency of "linear" geometry can optionally be controlled independently from the rest of the geometry via [[lineRgb]] and [[lineTransparency]].
* Linear geometry consists of any of the following:
* - Curves and line strings;
* - Points and point strings; and
* - The outlines of planar regions.
* The edges of 3d surfaces like spheres are not considered linear geometry.
*
* @see [[FeatureOverrides]] to customize the appearance of multiple features.
* @public
*/
export class FeatureAppearance {
/** Overrides the feature's color.
* If [[lineRgb]] is `undefined`, this color also applies to linear geometry.
*/
rgb;
/** If defined, overrides the color of linear geometry independently of [[rgb]].
* If `false`, linear geometry draws using its normal color; otherwise, it uses the specified color.
*/
lineRgb;
/** The width of lines and edges in pixels as an integer in [1, 31]. */
weight;
/** The transparency in the range [0, 1] where 0 indicates fully opaque and 1 indicates fully transparent.
* If [[lineTransparency]] is `undefined`, this transparency also applies to linear geometry.
* @see [[viewDependentTransparency]] for details on how this override interacts with the [DisplayStyle]($backend).
*/
transparency;
/** If defined, overrides the transparency of linear geometry independently of [[transparency]].
* If `false`, linear geometry draws using its normal transparency; otherwise, it uses the specified transparency.
* @see [[viewDependentTransparency]] for details on how this override interacts with the [DisplayStyle]($backend).
*/
lineTransparency;
/** The pixel pattern applied to lines and edges. */
linePixels;
/** If true, don't apply the [[RenderMaterial]] to the feature's surfaces. */
ignoresMaterial;
/** If true, the feature will not be drawn when using [Viewport.readPixels]($frontend), meaning [Tool]($frontend)s will not be able to interact with it. */
nonLocatable;
/** If true, the feature will be rendered using the [[Hilite.Settings]] defined by [Viewport.emphasisSettings]($frontend) to make it stand out. */
emphasized;
/** If true, then [[transparency]] and/or [[lineTransparency]] will only be applied if [[ViewFlags.transparency]] is enabled and the current [[RenderMode]] supports transparency.
* Default: false, meaning the transparency will be applied regardless of view flags or render mode.
* This property has no effect if this appearance does not override transparency.
*/
viewDependentTransparency;
/** An appearance that overrides nothing. */
static defaults = new FeatureAppearance({});
static fromJSON(props) {
if (!props || propsMatchDefaults(props)) {
return this.defaults;
}
return new FeatureAppearance(props);
}
/** Create a FeatureAppearance that overrides only the RGB color.
* @note The transparency component of the ColorDef is ignored.
*/
static fromRgb(color) {
return new FeatureAppearance({ rgb: RgbColor.fromColorDef(color) });
}
/** Create a FeatureAppearance that overrides the RGB and transparency.
* The appearance's transparency is derived from the transparency component of the ColorDef.
*/
static fromRgba(color, viewDependentTransparency = false) {
return new FeatureAppearance({
rgb: RgbColor.fromColorDef(color),
transparency: color.colors.t / 255,
viewDependentTransparency: viewDependentTransparency ? true : undefined,
});
}
/** Create a FeatureAppearance that overrides only the transparency */
static fromTransparency(transparencyValue, viewDependent = false) {
return new FeatureAppearance({
transparency: transparencyValue,
viewDependentTransparency: viewDependent ? true : undefined,
});
}
/** Create a FeatureAppearance with overrides corresponding to those defined by the supplied SubCategoryOverride.
* @note Subcategory overrides set [[viewDependentTransparency]] to `true`.
*/
static fromSubCategoryOverride(ovr) {
const rgb = undefined !== ovr.color ? RgbColor.fromColorDef(ovr.color) : undefined;
const transparency = ovr.transparency;
const weight = ovr.weight;
const ignoresMaterial = undefined !== ovr.material && Id64.isValid(ovr.material) ? true : undefined;
return this.fromJSON({ rgb, transparency, weight, ignoresMaterial, viewDependentTransparency: true });
}
/** Returns true if this appearance does not override any aspects of symbology. */
get matchesDefaults() {
return this.equals(FeatureAppearance.defaults);
}
get overridesRgb() { return undefined !== this.rgb; }
get overridesTransparency() { return undefined !== this.transparency; }
get overridesLinePixels() { return undefined !== this.linePixels; }
get overridesWeight() { return undefined !== this.weight; }
/** Get the color that will be applied to linear geometry, or undefined if not overridden.
* This is the same as [[rgb]] if [[lineRgb]] is `undefined`.
*/
getLineRgb() {
return false !== this.lineRgb ? (this.lineRgb ?? this.rgb) : undefined;
}
/** Get the transparency that will be applied to linear geometry, or undefined if not overridden.
* This is the same as [[transparency]] if [[lineTransparency]] is `undefined`.
*/
getLineTransparency() {
return false !== this.lineTransparency ? (this.lineTransparency ?? this.transparency) : undefined;
}
get overridesSymbology() {
return this.overridesRgb || this.overridesTransparency || this.overridesWeight || this.overridesLinePixels || !!this.ignoresMaterial
|| this.emphasized || this.overridesNonLocatable
|| undefined !== this.getLineRgb() || undefined !== this.getLineTransparency();
}
get overridesNonLocatable() { return undefined !== this.nonLocatable; }
get isFullyTransparent() {
const surf = this.transparency ?? 0;
const line = this.getLineTransparency() ?? 0;
return surf >= 1 && line >= 1;
}
/** Returns true if any aspect of the appearance is overridden (i.e., if any member is not undefined). */
get anyOverridden() { return this.overridesSymbology || this.overridesNonLocatable; }
equals(other) {
if (this === other)
return true;
return equalRgb(this.rgb, other.rgb)
&& this.weight === other.weight
&& equalTransparency(this.transparency, other.transparency)
&& this.linePixels === other.linePixels
&& this.ignoresMaterial === other.ignoresMaterial
&& this.nonLocatable === other.nonLocatable
&& this.emphasized === other.emphasized
&& this.viewDependentTransparency === other.viewDependentTransparency
&& equalLineTransparency(this.lineTransparency, other.lineTransparency)
&& equalLineRgb(this.lineRgb, other.lineRgb);
}
toJSON() {
const props = {};
if (this.rgb)
props.rgb = this.rgb.toJSON();
if (undefined !== this.weight)
props.weight = this.weight;
if (undefined !== this.transparency) {
props.transparency = this.transparency;
if (this.viewDependentTransparency)
props.viewDependentTransparency = true;
}
if (undefined !== this.linePixels)
props.linePixels = this.linePixels;
if (true === this.ignoresMaterial)
props.ignoresMaterial = true;
if (true === this.nonLocatable)
props.nonLocatable = true;
if (true === this.emphasized)
props.emphasized = true;
if (undefined !== this.lineTransparency)
props.lineTransparency = this.lineTransparency;
if (this.lineRgb) {
props.lineRgb = this.lineRgb;
if (this.viewDependentTransparency)
props.viewDependentTransparency = true;
}
return props;
}
/** Convert this appearance to JSON, and override any properties explicitly specified by `changedProps` in the result.
* Example:
* ```ts
* const base = FeatureAppearance.fromRgba(ColorDef.white); // transparency=0, rgb=white
* const clone = base.cloneProps({ transparency: undefined, weight: 5 }); // transparency=undefined, rgb=white, weight=5
* ```
* @see [[FeatureAppearance.clone]].
*/
cloneProps(changedProps) {
return {
...this.toJSON(),
...changedProps,
};
}
/** Create a copy of this appearance, overriding any properties explicitly specified by `changedProps`.
* Example:
* ```ts
* const base = FeatureAppearance.fromRgba(ColorDef.white); // transparency=0, rgb=white
* const clone = base.clone({ transparency: undefined, weight: 5 }); // transparency=undefined, rgb=white, weight=5
* ```
* @see [[FeatureAppearance.cloneProps]].
*/
clone(changedProps) {
return FeatureAppearance.fromJSON(this.cloneProps(changedProps));
}
/** Produce a FeatureAppearance from the supplied appearance in which any aspect not defined by the base appearance is overridden by this appearance. */
extendAppearance(base) {
if (!this.overridesSymbology)
return base;
const props = base.toJSON();
if (undefined === props.rgb)
props.rgb = this.rgb;
if (undefined === props.transparency)
props.transparency = this.transparency;
if (undefined === props.linePixels)
props.linePixels = this.linePixels;
if (undefined === props.weight)
props.weight = this.weight;
if (undefined === props.ignoresMaterial && this.ignoresMaterial)
props.ignoresMaterial = true;
if (undefined === props.nonLocatable && this.nonLocatable)
props.nonLocatable = true;
if (undefined === props.emphasized && this.emphasized)
props.emphasized = true;
if (!props.lineRgb)
props.lineRgb = this.lineRgb;
if (undefined === props.lineTransparency)
props.lineTransparency = this.lineTransparency;
if (this.viewDependentTransparency && (undefined !== props.transparency || undefined !== props.lineTransparency))
props.viewDependentTransparency = true;
return FeatureAppearance.fromJSON(props);
}
constructor(props) {
this.rgb = undefined !== props.rgb ? RgbColor.fromJSON(props.rgb) : undefined;
this.lineRgb = typeof props.lineRgb === "object" ? RgbColor.fromJSON(props.lineRgb) : (false === props.lineRgb ? false : undefined);
this.transparency = transparencyFromJSON(props.transparency);
this.lineTransparency = typeof props.lineTransparency === "number" ? transparencyFromJSON(props.lineTransparency) : (false === props.lineTransparency ? false : undefined);
this.weight = props.weight;
this.linePixels = props.linePixels;
this.ignoresMaterial = props.ignoresMaterial;
this.nonLocatable = props.nonLocatable;
this.emphasized = props.emphasized;
if (undefined !== this.weight) {
this.weight = Math.max(1, Math.min(this.weight, 32));
}
if (props.viewDependentTransparency && (undefined !== this.transparency || undefined !== this.getLineTransparency())) {
this.viewDependentTransparency = true;
}
}
}
const scratchIgnoreAnimationOverridesArgs = {
elementId: { lower: 0, upper: 0 },
animationNodeId: 0,
};
/** Specifies how to customize the appearance of individual [[Feature]]s, typically within the context of a [Viewport]($frontend).
* Individual aspects of a feature's appearance - like visibility, color, and transparency - are overridden by supplying a [[FeatureAppearance]].
* Those overrides can be specified on the basis of the feature's model, element, and/or subcategory. A default set of overrides can also be specified to
* apply to the appearance of any feature not otherwise overridden.
*
* It is possible to override multiple aspects of a feature on different bases. For example, you might specify that all features belonging to subcategory "A" should be drawn in red,
* and that all features belonging to model "B" should be drawn 50% transparent. In this case, a feature belonging to both subcategory "A" and model "B" will be drawn as 50% transparent red -
* the separate overrides are combined to produce the feature's overall appearance.
*
* In the case of conflicts, there is an order of precedence:
* - Model overrides take highest precedence.
* - Element overrides are of higher precedence than subcategory and animation overrides.
* - Overrides applied by a [[RenderSchedule.Script]]'s [[RenderSchedule.ElementTimeline]] are of higher precedence than subcategory overrides, but can be suppressed on a per-element basis via [[ignoreAnimationOverrides]].
* - Subcategory overrides have lowest precedence.
*
* For example, you might specify that all features belonging to subcategory "A" should be drawn in red, and all those belonging to model "B" should be drawn in green.
* Then a feature belonging to subcategory "A" and model "B" will be drawn in green, because the model overrides take precedence.
*
* Instances of this class are not typically instantiated by an application directly; instead, an application can implement a [FeatureOverrideProvider]($frontend)
* that augments the overrides supplied by a viewport.
*
* @see [FeatureSymbology.Overrides]($frontend) to create overrides specific to a [Viewport]($frontend) or [ViewState]($frontend).
* @see [FeatureOverrideProvider]($frontend) to customize the appearance of features within a [Viewport]($frontend).
* @public
*/
export class FeatureOverrides {
/** @internal */
_ignoreAnimationOverrides = [];
/** Ids of elements that should never be drawn. This takes precedence over [[alwaysDrawn]]. @internal */
_neverDrawn = new Id64.Uint32Set();
/** Ids of elements that should always be drawn. [[neverDrawn]] takes precedence over this set. @internal */
_alwaysDrawn = new Id64.Uint32Set();
/** If true, no elements *except* those defined in the "always drawn" set will be drawn.
* @see [[setAlwaysDrawn]]
*/
isAlwaysDrawnExclusive = false;
/** If true, the always-drawn elements are drawn even if their subcategories are not visible.
* @see [[setAlwaysDrawn]]
*/
alwaysDrawnIgnoresSubCategory = true;
/** If true, all subcategories are considered visible. This is used for drawing sheets via section callouts in the absence of an actual sheet view.
*/
ignoreSubCategory = false;
/** Overrides applied to any feature not explicitly overridden. @internal */
_defaultOverrides = FeatureAppearance.defaults;
/** Whether construction geometry should be drawn. */
_constructions = false;
/** Whether dimensions should be drawn. */
_dimensions = false;
/** Whether area patterns should be drawn. */
_patterns = false;
/** Whether line weights should be applied. If false, all lines are rendered 1-pixel wide. */
_lineWeights = true;
/** Overrides applied to all elements belonging to each model. @internal */
_modelOverrides = new Id64.Uint32Map();
/** Overrides applied to specific elements. */
_elementOverrides = new Id64.Uint32Map();
/** Overrides applied to geometry belonging to each subcategory. */
_subCategoryOverrides = new Id64.Uint32Map();
/** The set of displayed subcategories. Geometry belonging to subcategories not included in this set will not be drawn. */
_visibleSubCategories = new Id64.Uint32Set();
/** Display priorities assigned to subcategories, possibly overridden by display style. Only applicable for plan projection models. */
_subCategoryPriorities = new Id64.Uint32Map();
/** Per-model, a set of subcategories whose visibility should be inverted for elements within that model.
* Populated by Viewport.
*/
_modelSubCategoryOverrides = new Id64.Uint32Map();
/** Ids of animation nodes that should never be drawn. */
neverDrawnAnimationNodes = new Set();
/** Mapping of animation node Ids to overrides applied to the corresponding animation nodes.
* @internal
*/
animationNodeOverrides = new Map();
/** Accepts a criterion that determines whether color and transparency overrides originating from the view's [[RenderSchedule.Script]] should be ignored for a given element.
* The function receives a description of the element in question and returns `true` if the script's overrides should be ignored.
* Any number of such functions can be registered; if any one of them returns `true`, the script's overrides are not applied to the specified element.
*
* For example, applications commonly emphasize a set of elements by applying a [[FeatureAppearance.emphasized]] override to them, and specifying a highly-transparent
* default appearance to de-emphasize the rest of the elements in the view. If some of the de-emphasized elements' appearances are also being overridden by the schedule script, then
* they won't appear de-emphasized, making it difficult for the emphasized elements to stand out. In situations like this, [FeatureOverrideProvider]($frontend)s like [EmphasizeElements]($frontend) can register an [[IgnoreAnimationOverrides]] function that returns true if the element in question is not in the set of emphasized elements.
*/
ignoreAnimationOverrides(ignore) {
this._ignoreAnimationOverrides.push(ignore);
}
/** Overrides applied to features for which no other overrides are defined */
get defaultOverrides() { return this._defaultOverrides; }
/** Whether or not line weights are applied. If false, all lines are drawn with a weight of 1. */
get lineWeights() { return this._lineWeights; }
/** A set of elements that are always invisible.
* @note If an element is present in both `alwaysDrawn` and [[neverDrawn]], it will not be displayed - `neverDrawn` takes precedence.
*/
get neverDrawn() { return this._neverDrawn; }
/** A set of elements that are unconditionally displayed.
* @see [[isAlwaysDrawnExclusive]] to specify that *only* elements in this set will be displayed.
* @note If an element is present in both `alwaysDrawn` and [[neverDrawn]], it will not be displayed - `neverDrawn` takes precedence.
*/
get alwaysDrawn() { return this._alwaysDrawn; }
isNeverDrawn(elemIdLo, elemIdHi, animationNodeId) {
if (this._neverDrawn.has(elemIdLo, elemIdHi))
return true;
else
return this.neverDrawnAnimationNodes.has(animationNodeId);
}
isAlwaysDrawn(idLo, idHi) { return this._alwaysDrawn.has(idLo, idHi); }
/** Returns true if the [SubCategory]($backend) specified by Id is in the set of visible subcategories. */
isSubCategoryVisible(idLo, idHi) { return this._visibleSubCategories.has(idLo, idHi); }
/** Returns true if the [SubCategory]($backend) specified by Id is in the set of visible subcategories in the context of the specified model. */
isSubCategoryVisibleInModel(subcatLo, subcatHi, modelLo, modelHi) {
if (this.ignoreSubCategory)
return true;
let vis = this.isSubCategoryVisible(subcatLo, subcatHi);
const modelOvr = this._modelSubCategoryOverrides.get(modelLo, modelHi);
if (undefined !== modelOvr && modelOvr.has(subcatLo, subcatHi))
vis = !vis;
return vis;
}
getModelOverrides(idLo, idHi) {
return this._modelOverrides.get(idLo, idHi);
}
getElementAnimationOverrides(idLo, idHi, animationNodeId) {
if (this.animationNodeOverrides.size === 0)
return undefined;
// NB: An animation node Id of zero means "not animated". Some providers like EmphasizeElements may provide an appearance override for unanimated nodes.
// That should be preserved.
const app = this.animationNodeOverrides.get(animationNodeId);
if (!app || 0 === animationNodeId || this._ignoreAnimationOverrides.length === 0)
return app;
const args = scratchIgnoreAnimationOverridesArgs;
args.elementId.lower = idLo;
args.elementId.upper = idHi;
args.animationNodeId = animationNodeId;
return this._ignoreAnimationOverrides.some((ignore) => ignore(args)) ? undefined : app;
}
getElementOverrides(idLo, idHi, animationNodeId) {
const elemApp = this._elementOverrides.get(idLo, idHi);
const nodeApp = this.getElementAnimationOverrides(idLo, idHi, animationNodeId);
if (elemApp)
return nodeApp ? nodeApp.extendAppearance(elemApp) : elemApp;
return nodeApp;
}
getSubCategoryOverrides(idLo, idHi) { return this._subCategoryOverrides.get(idLo, idHi); }
/** Add a [SubCategory]($backend) to the set of visible subcategories. */
setVisibleSubCategory(id) { this._visibleSubCategories.addId(id); }
/** Specify the Id of an element that should never be drawn. */
setNeverDrawn(id) { this._neverDrawn.addId(id); }
/** Specify the Id of an element that should always be drawn. */
setAlwaysDrawn(id) { this._alwaysDrawn.addId(id); }
/** Specify the Id of a animation node that should never be drawn. */
setAnimationNodeNeverDrawn(id) { this.neverDrawnAnimationNodes.add(id); }
/** Specify the Ids of elements that should never be drawn. */
setNeverDrawnSet(ids) { copyIdSetToUint32Set(this._neverDrawn, ids); }
/** Specify the Ids of elements that should always be drawn. */
setAlwaysDrawnSet(ids, exclusive, ignoreSubCategory = true) {
copyIdSetToUint32Set(this._alwaysDrawn, ids);
this.isAlwaysDrawnExclusive = exclusive;
this.alwaysDrawnIgnoresSubCategory = ignoreSubCategory;
}
/** Returns the feature's appearance overrides, or undefined if the feature is not visible. */
getFeatureAppearance(feature, modelId, type = BatchType.Primary, animationNodeId = 0) {
return this.getAppearance(Id64.getLowerUint32(feature.elementId), Id64.getUpperUint32(feature.elementId), Id64.getLowerUint32(feature.subCategoryId), Id64.getUpperUint32(feature.subCategoryId), feature.geometryClass, Id64.getLowerUint32(modelId), Id64.getUpperUint32(modelId), type, animationNodeId);
}
static _weight1Appearance = FeatureAppearance.fromJSON({ weight: 1 });
/** Returns a feature's appearance overrides, or undefined if the feature is not visible.
* Takes Id64s as pairs of unsigned 32-bit integers for efficiency, because that is how they are stored by the PackedFeatureTable associated with each batch of graphics.
* @see [[getFeatureAppearance]] for an equivalent function that accepts [Id64String]($core-bentley)s instead of integer pairs.
*/
getAppearance(elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) {
if (BatchType.VolumeClassifier === type || BatchType.PlanarClassifier === type)
return this.getClassifierAppearance(elemLo, elemHi, subcatLo, subcatHi, modelLo, modelHi, animationNodeId);
let app = !this._lineWeights ? FeatureOverrides._weight1Appearance : FeatureAppearance.defaults;
const modelApp = this.getModelOverrides(modelLo, modelHi);
if (undefined !== modelApp)
app = modelApp.extendAppearance(app);
// Is the element visible?
let elemApp, alwaysDrawn = false;
if (Id64.isValidUint32Pair(elemLo, elemHi)) {
if (this.isNeverDrawn(elemLo, elemHi, animationNodeId))
return undefined;
alwaysDrawn = this.isAlwaysDrawn(elemLo, elemHi);
if (!alwaysDrawn && this.isAlwaysDrawnExclusive)
return undefined;
// Element overrides take precedence
elemApp = this.getElementOverrides(elemLo, elemHi, animationNodeId);
if (undefined !== elemApp)
app = undefined !== modelApp ? elemApp.extendAppearance(app) : elemApp;
}
let subCatApp;
if (!this.ignoreSubCategory && Id64.isValidUint32Pair(subcatLo, subcatHi)) {
if ((!alwaysDrawn || !this.alwaysDrawnIgnoresSubCategory) && !this.isSubCategoryVisibleInModel(subcatLo, subcatHi, modelLo, modelHi))
return undefined;
subCatApp = this.getSubCategoryOverrides(subcatLo, subcatHi);
if (undefined !== subCatApp)
app = subCatApp.extendAppearance(app);
}
// Only apply default if *no* appearance was explicitly registered (doesn't matter if registered appearance does not actually override anything)
if (undefined === elemApp && undefined === modelApp && undefined === subCatApp)
app = this._defaultOverrides.extendAppearance(app);
let visible = alwaysDrawn || this.isClassVisible(geomClass);
if (visible && app.isFullyTransparent)
visible = false; // don't bother rendering something with full transparency...
return visible ? app : undefined;
}
/** Classifiers behave totally differently...in particular they are never invisible unless fully-transparent. */
getClassifierAppearance(elemLo, elemHi, subcatLo, subcatHi, modelLo, modelHi, animationNodeId) {
let app = FeatureAppearance.defaults;
const modelApp = this.getModelOverrides(modelLo, modelHi);
if (undefined !== modelApp)
app = modelApp.extendAppearance(app);
const elemApp = this.getElementOverrides(elemLo, elemHi, animationNodeId);
if (undefined !== elemApp)
app = undefined !== modelApp ? elemApp.extendAppearance(app) : elemApp;
if (!this.ignoreSubCategory && Id64.isValidUint32Pair(subcatLo, subcatHi)) {
const subCat = this.getSubCategoryOverrides(subcatLo, subcatHi);
if (undefined !== subCat)
app = subCat.extendAppearance(app);
}
if (undefined === elemApp && undefined === modelApp)
app = this._defaultOverrides.extendAppearance(app);
// NB: A fully-transparent classifier means the classifier is a clip mask - classified pixels will be discarded.
return app;
}
/** Return whether geometry of the specified class should be drawn.
* @see [[ViewFlags.constructions]], [[ViewFlags.dimensions]], and [[ViewFlags.patterns]].
*/
isClassVisible(geomClass) {
switch (geomClass) {
case GeometryClass.Construction: return this._constructions;
case GeometryClass.Dimension: return this._dimensions;
case GeometryClass.Pattern: return this._patterns;
default: return true;
}
}
/** Specify overrides for all elements belonging to a specified [GeometricModel]($backend), or all geometry belonging to a specified [GeometricElement]($backend) or [SubCategory]($backend). */
override(args) {
let id;
let map;
if (undefined !== args.elementId) {
id = args.elementId;
map = this._elementOverrides;
}
else if (undefined !== args.modelId) {
id = args.modelId;
map = this._modelOverrides;
}
else {
id = args.subCategoryId;
map = this._subCategoryOverrides;
}
let app = args.appearance;
const idLo = Id64.getLowerUint32(id);
const idHi = Id64.getUpperUint32(id);
if (undefined !== args.elementId && this.isNeverDrawn(idLo, idHi, 0))
return;
const replace = "replace" === args.onConflict;
const existing = replace ? undefined : map.get(idLo, idHi);
if (existing) {
assert("replace" !== args.onConflict);
switch (args.onConflict) {
case "skip":
return;
case "extend":
app = app.extendAppearance(existing);
break;
default:
app = existing.extendAppearance(app);
break;
}
}
map.set(idLo, idHi, app);
}
/** Specify overrides for all geometry originating from the specified animation node.
* @param id The Id of the animation node.
* @param app The symbology overrides.
* @note These overrides do not take precedence over element overrides.
*/
overrideAnimationNode(id, app) {
this.animationNodeOverrides.set(id, app);
}
/** Defines a default appearance to be applied to any [[Feature]] *not* explicitly overridden.
* @param appearance The symbology overrides.
* @param replaceExisting Specifies whether to replace the current default overrides if they are already defined.
*/
setDefaultOverrides(appearance, replaceExisting = true) {
if (replaceExisting || !appearance.overridesSymbology)
this._defaultOverrides = appearance;
}
/** Get the display priority of a subcategory. This is only relevant when using [[PlanProjectionSettings]]. */
getSubCategoryPriority(idLo, idHi) {
return this._subCategoryPriorities.get(idLo, idHi) ?? 0;
}
/** Adds all fully transparent elements to the _neverDrawn set. This is used for BatchedModels planar masks. */
addInvisibleElementOverridesToNeverDrawn() {
this._elementOverrides.forEach((lo, hi) => {
const app = this.getElementOverrides(lo, hi, 0);
if (app?.isFullyTransparent)
this._neverDrawn.add(lo, hi);
});
}
/** Construct a new Overrides that overrides nothing.
* @see [FeatureSymbology.Overrides]($frontend) to construct overrides based on a [ViewState]($frontend) or [Viewport]($frontend).
*/
constructor() {
//
}
/** Returns true if geometry belonging to the specified subcategory will be drawn. */
isSubCategoryIdVisible(id) { return this.isSubCategoryVisible(Id64.getLowerUint32(id), Id64.getUpperUint32(id)); }
/** Returns the overrides applied to geometry belonging to the specified model, if any such are defined. */
getModelOverridesById(id) { return this.getModelOverrides(Id64.getLowerUint32(id), Id64.getUpperUint32(id)); }
/** Returns the overrides applied to geometry belonging to the specified element, if any such are defined. */
getElementOverridesById(id) { return this.getElementOverrides(Id64.getLowerUint32(id), Id64.getUpperUint32(id), 0); }
/** Returns the overrides applied to geometry belonging to the specified subcategory, if any such are defined. */
getSubCategoryOverridesById(id) { return this.getSubCategoryOverrides(Id64.getLowerUint32(id), Id64.getUpperUint32(id)); }
/** Returns true if the specified Feature will be drawn. */
isFeatureVisible(feature) {
const { elementId, subCategoryId, geometryClass } = feature;
const isValidElemId = !Id64.isInvalid(elementId);
const elemIdParts = isValidElemId ? Id64.getUint32Pair(elementId) : undefined;
if (undefined !== elemIdParts && this.isNeverDrawn(elemIdParts.lower, elemIdParts.upper, 0))
return false;
const alwaysDrawn = undefined !== elemIdParts && this.isAlwaysDrawn(elemIdParts.lower, elemIdParts.upper);
if (alwaysDrawn || this.isAlwaysDrawnExclusive)
return alwaysDrawn;
// NB: This ignores per-model subcategory visibility overrides, because caller did not specify a model.
if (!this.isSubCategoryIdVisible(subCategoryId))
return false;
return this.isClassVisible(geometryClass);
}
}
/** @public */
export var FeatureAppearanceProvider;
(function (FeatureAppearanceProvider) {
/** Produce a FeatureAppearanceSource for which `getAppearance()` returns the appearance specified in `source`, potentially modified by `provider`. */
function wrap(source, provider) {
return {
getAppearance: (elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) => {
return provider.getFeatureAppearance(source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
},
};
}
/** Create a provider that obtains each feature's appearance from the source, and if the feature is visible, modifies the appearance.
* @param supplementAppearance A function accepting the feature's base appearance and returning a supplemental appearance.
* @public
*/
function supplement(supplementAppearance) {
return {
getFeatureAppearance: (source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) => {
const app = source.getAppearance(elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
return app ? supplementAppearance(app) : app;
},
};
}
FeatureAppearanceProvider.supplement = supplement;
/** Chain two FeatureAppearanceProviders together such that `first`'s `getFeatureAppearance` function is applied before `second`'s.
* If `second` invokes `source.getAppearance()`, the returned appearance will include any modifications applied by `first`.
* @public
*/
function chain(first, second) {
if (first === second)
return first;
return {
getFeatureAppearance: (source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) => {
return second.getFeatureAppearance(wrap(source, first), elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
},
};
}
FeatureAppearanceProvider.chain = chain;
})(FeatureAppearanceProvider || (FeatureAppearanceProvider = {}));
//# sourceMappingURL=FeatureSymbology.js.map