@itwin/core-common
Version:
iTwin.js components common to frontend and backend
352 lines • 16.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 DisplayStyles
*/
import { assert } from "@itwin/core-bentley";
/** Describes how a [[SpatialClassifier]] affects the display of classified geometry - that is, geometry intersecting
* the classifier.
* @public
* @extensions
*/
export var SpatialClassifierInsideDisplay;
(function (SpatialClassifierInsideDisplay) {
/** The geometry is not displayed. */
SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["Off"] = 0] = "Off";
/** The geometry is displayed without alteration. */
SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["On"] = 1] = "On";
/** The geometry is darkened. */
SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["Dimmed"] = 2] = "Dimmed";
/** The geometry is tinted by the [Viewport.hilite]($frontend) color. */
SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["Hilite"] = 3] = "Hilite";
/** The geometry is tinted with the colors of the classifier elements. */
SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["ElementColor"] = 4] = "ElementColor";
})(SpatialClassifierInsideDisplay || (SpatialClassifierInsideDisplay = {}));
/** Describes how a [[SpatialClassifier]] affects the display of unclassified geometry - that is, geometry not intersecting
* the classifier.
* @public
* @extensions
*/
export var SpatialClassifierOutsideDisplay;
(function (SpatialClassifierOutsideDisplay) {
/** The geometry is not displayed. */
SpatialClassifierOutsideDisplay[SpatialClassifierOutsideDisplay["Off"] = 0] = "Off";
/** The geometry is displayed without alteration. */
SpatialClassifierOutsideDisplay[SpatialClassifierOutsideDisplay["On"] = 1] = "On";
/** The geometry is darkened. */
SpatialClassifierOutsideDisplay[SpatialClassifierOutsideDisplay["Dimmed"] = 2] = "Dimmed";
})(SpatialClassifierOutsideDisplay || (SpatialClassifierOutsideDisplay = {}));
/** Flags affecting how a [[SpatialClassifier]] is applied.
* @public
*/
export class SpatialClassifierFlags {
/** How geometry intersecting the classifier should be displayed. */
inside;
/** How geometry not intersecting the classifier should be displayed. */
outside;
/** True for volume classification; false for planar classification. */
isVolumeClassifier;
/** Construct new flags. */
constructor(inside = SpatialClassifierInsideDisplay.ElementColor, outside = SpatialClassifierOutsideDisplay.Dimmed, isVolumeClassifier = false) {
this.inside = insideDisplay(inside);
this.outside = outsideDisplay(outside);
this.isVolumeClassifier = isVolumeClassifier;
}
/** Construct from JSON representation. */
static fromJSON(props) {
return new SpatialClassifierFlags(props.inside, props.outside, true === props.isVolumeClassifier);
}
/** Convert to JSON representation. */
toJSON() {
const props = {
inside: this.inside,
outside: this.outside,
};
if (this.isVolumeClassifier)
props.isVolumeClassifier = true;
return props;
}
/** Create flags indentical to these ones except for any properties explicitly specified by `changedProps`. */
clone(changedProps) {
if (!changedProps)
return this;
return SpatialClassifierFlags.fromJSON({ ...this.toJSON(), ...changedProps });
}
/** Return true if these flags are equivalent to `other`. */
equals(other) {
if (other === this)
return true;
return other.inside === this.inside && other.outside === this.outside && other.isVolumeClassifier === this.isVolumeClassifier;
}
/** Return true if these flags are equivalent to `props`. */
equalsProps(props) {
return this.inside === props.inside && this.outside === props.outside && this.isVolumeClassifier === (true === props.isVolumeClassifier);
}
}
/** Describes how to use the geometry of one [GeometricModel]($backend) to classify the contents of other models - most typically, reality models.
* Applying a classifier divides the geometry of the classified model into two groups:
* - Classified (intersecting the classifier); and
* - Unclassified (not intersecting the classifier).
* For example, a model containing the building footprints for a city block could be used to classify a reality mesh captured from photographs of the
* real-world block. Then, buildings within the reality mesh can be selected individually, and present the properties of the classifier geometry (e.g.,
* the address of the building). The appearance of the geometry can also be customized based using [[SpatialClassifierInsideDisplay]] and [[SpatialClassifierOutsideDisplay]].
* Two types of classification are supported:
* - Planar classification, in which the geometry of the classifier model is projected onto a plane to classify geometry within a region extruded perpendicular
* the plane (e.g., the building footprints example); and
* - Volume classification, in which closed, non-intersecting volumes within the classifier classify geometry that intersects (i.e. is contained within) those same volumes (e.g., imagine using boxes instead
* of footprints to classify buildings, or floors of buildings).
* @see this (interactive example)[https://www.itwinjs.org/sample-showcase/?group=Viewer+Features&sample=classifier-sample].
* @see [[SpatialClassifiers]] to define a set of classifiers.
* @see [[ContextRealityModel.classifiers]] to classify a context reality model.
* @see [SpatialModelState.classifiers]($frontend) to classify a persistent reality model.
* @public
*/
export class SpatialClassifier {
/** The Id of the [GeometricModel]($backend) whose geometry is used to produce the classifier. */
modelId;
/** A distance in meters by which to expand the classifier geometry. For example, if line strings are used to represent streets,
* you might expand them to the average width of a street.
*/
expand;
/** Flags controlling how to apply the classifier. */
flags;
/** A user-friendly name, useful for identifying individual classifiers within a [[SpatialClassifiers]]. */
name;
/** Construct a new classifier. */
constructor(modelId, name, flags = new SpatialClassifierFlags(), expand = 0) {
this.modelId = modelId;
this.expand = expand;
this.flags = flags;
this.name = name;
}
/** Construct from JSON representation. */
static fromJSON(props) {
return new SpatialClassifier(props.modelId, props.name, SpatialClassifierFlags.fromJSON(props.flags), props.expand);
}
/** Convert to JSON representation.
* @note This method always sets the [[SpatialClassifierProps.isActive]] property to `false`.
*/
toJSON() {
return {
modelId: this.modelId,
expand: this.expand,
flags: this.flags.toJSON(),
name: this.name,
isActive: false,
};
}
/** Construct from Model Map Layer.
* @beta
*/
static fromModelMapLayer(mapLayer) {
const flags = SpatialClassifierFlags.fromJSON({ inside: SpatialClassifierInsideDisplay.Off, outside: SpatialClassifierOutsideDisplay.Off });
return new SpatialClassifier(mapLayer.modelId, mapLayer.name, flags);
}
/** Create a classifier identical to this one except for any properties explicitly specified by `changedProps`. */
clone(changedProps) {
if (!changedProps)
return this;
return SpatialClassifier.fromJSON({ ...this.toJSON(), ...changedProps });
}
/** Return true if this classifier is equivalent to `other`. */
equals(other) {
if (other === this)
return true;
return this.modelId === other.modelId && this.expand === other.expand && this.name === other.name && this.flags.equals(other.flags);
}
/** Return true if this classifier is equivalent to `props`. */
equalsProps(props) {
return this.modelId === props.modelId && this.expand === props.expand && this.name === props.name && this.flags.equalsProps(props.flags);
}
}
/** A set of [[SpatialClassifier]]s for a given reality model. At most one of the classifiers can be actively classifying the model at any given time.
* The set of classifiers can be presented to the user, listed by name, so that the active classifier can be changed.
* The set of classifiers is populated from its JSON representation and that representation is kept in sync as the set of classifiers is modified.
* @see this (interactive example)[https://www.itwinjs.org/sample-showcase/?group=Viewer+Features&sample=classifier-sample].
* @see [[SpatialClassifier]] for details on how spatial classification works.
* @see [[ContextRealityModel.classifiers]] to define classifiers for a context reality model.
* @see [SpatialModelState.classifiers]($frontend) to define classifiers for a persistent reality model.
* @public
*/
export class SpatialClassifiers {
_json;
_classifiers = [];
_active;
/** Construct a new set of classifiers from the JSON representation. The set will be initialized from `container.classifiers` and that JSON representation
* will be kept in sync with changes made to the set. The caller should not directly modify `container.classifiers` or its contents as that will cause the set to become out
* of sync with the JSON representation.
* The [[active]] classifier will be determined by the first [[SpatialClassifierProps]] whose `isActive` property is set to `true`, if any.
*/
constructor(container) {
this._json = container;
const json = this._array;
if (!json)
return;
for (const props of json) {
const classifier = SpatialClassifier.fromJSON(props);
this._classifiers.push(classifier);
if (props.isActive) {
if (!this._active)
this._active = classifier;
else
props.isActive = false;
}
}
}
/** The classifier currently classifying the target reality model. The classifier passed to the setter must be one obtained from this set, or one equivalent to
* one contained in this set; in the latter case, the equivalent classifier contained in this set becomes active.
*/
/** The classifier currently classifying the target reality model, if any.
* @see [[setActive]] to change the active classifier.
*/
get active() {
return this._active;
}
/** Change the [[active]] classifier. The input must be a classifier belonging to this set, or equivalent to one in the set.
* If no equivalent classifier exists in the set, the active classifier remains unchanged.
* @param The classifier to set as active, or `undefined` to clear the active classifier.
* @returns the active classifier.
*/
setActive(active) {
const array = this._array;
if (!array)
return this.active;
if (active) {
active = this.findEquivalent(active);
if (!active)
return this.active;
}
if (active === this.active)
return this.active;
let propsIndex = -1;
if (active) {
propsIndex = array.findIndex((x) => active.equalsProps(x));
if (-1 === propsIndex)
return this.active;
}
this._active = active;
for (let i = 0; i < array.length; i++)
array[i].isActive = (i === propsIndex);
return this.active;
}
/** Obtain an iterator over the classifiers contained in this set. */
[Symbol.iterator]() {
return this._classifiers[Symbol.iterator]();
}
/** The number of classifiers in this set. */
get size() {
return this._array?.length ?? 0;
}
/** Returns the first classifier that satisfies `criterion`, or `undefined` if no classifier satisfies it. */
find(criterion) {
return this._classifiers.find(criterion);
}
/** Find the first classifier that is equivalent to the supplied classifier, or `undefined` if no equivalent classifier exists in this set. */
findEquivalent(classifier) {
return this.find((x) => x.equals(classifier));
}
/** Return true if the specified classifier or one equivalent to it exists in this set. */
has(classifier) {
return undefined !== this.findEquivalent(classifier);
}
/** Add a classifier to this set. If an equivalent classifier already exists, the supplied classifier is not added.
* @param classifier The classifier to add.
* @returns The equivalent pre-existing classifier, if one existed; or the supplied classifier, if it was added to the set.
*/
add(classifier) {
const existing = this.findEquivalent(classifier);
if (existing)
return existing;
let array = this._array;
if (!array)
array = this._json.classifiers = [];
this._classifiers.push(classifier);
array.push(classifier.toJSON());
return classifier;
}
/** Replace an existing classifier with a different one.
* @param toReplace The classifier to be replaced.
* @param replacement The classifier to replace `toReplace`.
* @returns true if a classifier equivalent to `toReplace` existed in the set and was replaced by `replacement`.
* @note If `toReplace` was the [[active]] classifier, `replacement` will become active.
*/
replace(toReplace, replacement) {
const list = this._array;
if (!list)
return false;
const classifierIndex = this._classifiers.findIndex((x) => x.equals(toReplace));
if (-1 === classifierIndex)
return false;
const propsIndex = list.findIndex((x) => toReplace.equalsProps(x));
assert(propsIndex === classifierIndex);
if (-1 === propsIndex)
return false;
toReplace = this._classifiers[classifierIndex];
const wasActive = this.active === toReplace;
this._classifiers[classifierIndex] = replacement;
const props = list[propsIndex] = replacement.toJSON();
if (wasActive) {
props.isActive = true;
this._active = replacement;
}
return true;
}
/** Remove the first classifier equivalent to `classifier` from this set.
* @param classifier The classifier to remove.
* @returns The classifier that was actually removed, or `undefined` if none was removed.
*/
delete(classifier) {
const list = this._array;
if (!list)
return undefined;
const classifierIndex = this._classifiers.findIndex((x) => x.equals(classifier));
if (-1 === classifierIndex)
return undefined;
classifier = this._classifiers[classifierIndex];
const propsIndex = list.findIndex((x) => classifier.equalsProps(x));
assert(propsIndex === classifierIndex);
if (-1 === propsIndex)
return undefined;
list.splice(propsIndex, 1);
this._classifiers.splice(classifierIndex, 1);
if (list.length === 0)
this._json.classifiers = undefined;
if (classifier === this.active)
this._active = undefined;
return classifier;
}
/** Remove all classifiers from this set. */
clear() {
this._classifiers.length = 0;
this._json.classifiers = undefined;
this._active = undefined;
}
get _array() {
return Array.isArray(this._json.classifiers) ? this._json.classifiers : undefined;
}
}
function insideDisplay(display) {
switch (display) {
case SpatialClassifierInsideDisplay.Off:
case SpatialClassifierInsideDisplay.On:
case SpatialClassifierInsideDisplay.Dimmed:
case SpatialClassifierInsideDisplay.Hilite:
case SpatialClassifierInsideDisplay.ElementColor:
return display;
default:
return SpatialClassifierInsideDisplay.ElementColor;
}
}
function outsideDisplay(display) {
switch (display) {
case SpatialClassifierOutsideDisplay.Off:
case SpatialClassifierOutsideDisplay.On:
case SpatialClassifierOutsideDisplay.Dimmed:
return display;
default:
return SpatialClassifierOutsideDisplay.Dimmed;
}
}
//# sourceMappingURL=SpatialClassification.js.map