@itwin/core-frontend
Version:
iTwin.js frontend components
236 lines • 11.7 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 Views
*/
import { BeEvent, CompressedId64Set } from "@itwin/core-bentley";
import { Constant, Range3d } from "@itwin/core-geometry";
import { AuxCoordSystemSpatialState } from "./AuxCoordSys";
import { ModelSelectorState } from "./ModelSelectorState";
import { CategorySelectorState } from "./CategorySelectorState";
import { DisplayStyle3dState } from "./DisplayStyleState";
import { ViewState3d } from "./ViewState";
import { SpatialTileTreeReferences } from "./tile/internal";
/** Defines a view of one or more SpatialModels.
* The list of viewed models is stored in the ModelSelector.
* @public
* @extensions
*/
export class SpatialViewState extends ViewState3d {
static get className() { return "SpatialViewDefinition"; }
_treeRefs;
_modelSelector;
_unregisterModelSelectorListeners = [];
/** An event raised when the set of models viewed by this view changes, *only* if the view is attached to a [[Viewport]].
* @public
*/
onViewedModelsChanged = new BeEvent();
get modelSelector() {
return this._modelSelector;
}
set modelSelector(selector) {
if (selector === this.modelSelector)
return;
const isAttached = this.isAttachedToViewport;
this.unregisterModelSelectorListeners();
this._modelSelector = selector;
if (isAttached) {
this.registerModelSelectorListeners();
this.onViewedModelsChanged.raiseEvent();
}
this.markModelSelectorChanged();
}
/** Create a new *blank* SpatialViewState. The returned SpatialViewState will nave non-persistent empty [[CategorySelectorState]] and [[ModelSelectorState]],
* and a non-persistent [[DisplayStyle3dState]] with default values for all of its components. Generally after creating a blank SpatialViewState,
* callers will modify the state to suit specific needs.
* @param iModel The IModelConnection for the new SpatialViewState
* @param origin The origin for the new SpatialViewState
* @param extents The extents for the new SpatialViewState
* @param rotation The rotation of the new SpatialViewState. If undefined, use top view.
* @public
*/
static createBlank(iModel, origin, extents, rotation) {
const blank = {};
const cat = new CategorySelectorState(blank, iModel);
const modelSelectorState = new ModelSelectorState(blank, iModel);
const displayStyleState = new DisplayStyle3dState(blank, iModel);
const view = new this(blank, iModel, cat, displayStyleState, modelSelectorState);
view.setOrigin(origin);
view.setExtents(extents);
if (undefined !== rotation)
view.setRotation(rotation);
return view;
}
static createFromProps(props, iModel) {
const cat = new CategorySelectorState(props.categorySelectorProps, iModel);
const displayStyleState = new DisplayStyle3dState(props.displayStyleProps, iModel);
const modelSelectorState = new ModelSelectorState(props.modelSelectorProps, iModel);
return new this(props.viewDefinitionProps, iModel, cat, displayStyleState, modelSelectorState);
}
toProps() {
const props = super.toProps();
props.modelSelectorProps = this.modelSelector.toJSON();
return props;
}
constructor(props, iModel, arg3, displayStyle, modelSelector) {
super(props, iModel, arg3, displayStyle);
this._modelSelector = modelSelector;
if (arg3 instanceof SpatialViewState) // from clone
this._modelSelector = arg3.modelSelector.clone();
this._treeRefs = SpatialTileTreeReferences.create(this);
}
isSpatialView() { return true; }
equals(other) { return super.equals(other) && this.modelSelector.equals(other.modelSelector); }
createAuxCoordSystem(acsName) { return AuxCoordSystemSpatialState.createNew(acsName, this.iModel); }
get defaultExtentLimits() { return { min: Constant.oneMillimeter, max: 3 * Constant.diameterOfEarth }; } // Increased max by 3X to support globe mode.
/** @internal */
markModelSelectorChanged() {
this._treeRefs.update();
}
computeBaseExtents() {
const extents = Range3d.fromJSON(this.iModel.projectExtents);
// Ensure geometry coincident with planes of the project extents is not clipped.
extents.scaleAboutCenterInPlace(1.0001);
// Ensure ground plane is not clipped, if it's being drawn.
extents.extendRange(this.getGroundExtents());
return extents;
}
/** Compute a volume in world coordinates tightly encompassing the contents of the view. The volume is computed from the union of the volumes of the
* view's viewed models, including [GeometricModel]($backend)s and reality models.
* Those volumes are obtained from the [[TileTree]]s used to render those models, so any tile tree that has not yet been loaded will not contribute to the computation.
* If `options.baseExtents` is defined, it will be unioned with the computed volume.
* If the computed volume is null (empty), a default volume will be computed from [IModel.projectExtents]($common), which may be a looser approximation of the
* models' volumes.
* @param options Options used to customize how the volume is computed.
* @returns A non-null volume in world coordinates encompassing the contents of the view.
*/
computeFitRange(options) {
// Fit to the union of the ranges of all loaded tile trees.
const range = options?.baseExtents?.clone() ?? new Range3d();
for (const ref of this.getTileTreeRefs()) {
ref.unionFitRange(range);
}
// Fall back to the project extents if necessary.
if (range.isNull)
range.setFrom(this.computeBaseExtents());
// Avoid ridiculously small extents.
range.ensureMinLengths(1.0);
return range;
}
getViewedExtents() {
// Encompass the project extents and ground plane.
const extents = this.computeBaseExtents();
// Include any tile trees that extend outside the project extents.
extents.extendRange(this.computeFitRange());
return extents;
}
toJSON() {
const val = super.toJSON();
val.modelSelectorId = this.modelSelector.id;
return val;
}
/** @internal */
preload(hydrateRequest) {
super.preload(hydrateRequest);
const notLoaded = this.iModel.models.filterLoaded(this.modelSelector.models);
if (undefined === notLoaded)
return; // all requested models are already loaded
hydrateRequest.notLoadedModelSelectorStateModels = CompressedId64Set.sortAndCompress(notLoaded);
}
/** @internal */
async postload(hydrateResponse) {
const promises = [];
promises.push(super.postload(hydrateResponse));
if (hydrateResponse.modelSelectorStateModels !== undefined)
promises.push(this.iModel.models.updateLoadedWithModelProps(hydrateResponse.modelSelectorStateModels));
await Promise.all(promises);
}
viewsModel(modelId) { return this.modelSelector.containsModel(modelId); }
clearViewedModels() { this.modelSelector.models.clear(); }
addViewedModel(id) { this.modelSelector.addModels(id); }
removeViewedModel(id) { this.modelSelector.dropModels(id); }
forEachModel(func) {
for (const modelId of this.modelSelector.models) {
const model = this.iModel.models.getLoaded(modelId);
if (undefined !== model && undefined !== model.asGeometricModel3d)
func(model);
}
}
/** @internal */
*getModelTreeRefs() {
for (const ref of this._treeRefs) {
yield ref;
}
}
/** @internal */
createScene(context) {
super.createScene(context);
context.textureDrapes.forEach((drape) => drape.collectGraphics(context));
context.viewport.target.updateSolarShadows(this.getDisplayStyle3d().wantShadows ? context : undefined);
}
/** See [[ViewState.attachToViewport]]. */
attachToViewport(args) {
super.attachToViewport(args);
this.registerModelSelectorListeners();
this._treeRefs.attachToViewport(args);
}
/** See [[ViewState.detachFromViewport]]. */
detachFromViewport() {
super.detachFromViewport();
this._treeRefs.detachFromViewport();
this.unregisterModelSelectorListeners();
}
/** Chiefly for debugging: change the "deactivated" state of one or more tile tree references. Deactivated references are
* omitted when iterating the references, so e.g. their graphics are omitted from the scene.
* @param modelIds The Ids of one or more models whose tile tree references are to be affected. If omitted, all models are affected.
* @param deactivated True to deactivate the specified references, false to reactivate them, undefined to invert each one's current state.
* @param which The references to be affected as either a broad category or one or more indices of animated references.
* @internal
*/
setTileTreeReferencesDeactivated(modelIds, deactivated, which) {
this._treeRefs.setDeactivated(modelIds, deactivated, which);
}
/** For getting the [TileTreeReference]s that are in the modelIds, for planar classification.
* @param modelIds modelIds for which to get the TileTreeReferences
* @param maskTreeRefs where to store the TileTreeReferences
* @param maskRange range to extend for the maskRefs
* @internal
*/
collectMaskRefs(modelIds, maskTreeRefs, maskRange) {
this._treeRefs.collectMaskRefs(modelIds, maskTreeRefs, maskRange);
}
/** For getting a list of modelIds which do not participate in masking for planar classification.
* @param maskModels models which DO participate in planar clip masking
* @param useVisible when true, use visible models to set flag
* @internal
*/
getModelsNotInMask(maskModels, useVisible) {
return this._treeRefs.getModelsNotInMask(maskModels, useVisible);
}
registerModelSelectorListeners() {
const models = this.modelSelector.observableModels;
const func = () => {
this.markModelSelectorChanged();
this.onViewedModelsChanged.raiseEvent();
};
this._unregisterModelSelectorListeners.push(models.onAdded.addListener(func));
this._unregisterModelSelectorListeners.push(models.onDeleted.addListener(func));
this._unregisterModelSelectorListeners.push(models.onCleared.addListener(func));
}
unregisterModelSelectorListeners() {
this._unregisterModelSelectorListeners.forEach((f) => f());
this._unregisterModelSelectorListeners.length = 0;
}
}
/** Defines a spatial view that displays geometry on the image plane using a parallel orthographic projection.
* @public
* @extensions
*/
export class OrthographicViewState extends SpatialViewState {
static get className() { return "OrthographicViewDefinition"; }
constructor(props, iModel, categories, displayStyle, modelSelector) { super(props, iModel, categories, displayStyle, modelSelector); }
supportsCamera() { return false; }
}
//# sourceMappingURL=SpatialViewState.js.map