UNPKG

@itwin/core-frontend

Version:
236 lines • 11.7 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 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