"use strict";
/*---------------------------------------------------------------------------------------------
* 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
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrthographicViewState = exports.SpatialViewState = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const AuxCoordSys_1 = require("./AuxCoordSys");
const ModelSelectorState_1 = require("./ModelSelectorState");
const CategorySelectorState_1 = require("./CategorySelectorState");
const DisplayStyleState_1 = require("./DisplayStyleState");
const ViewState_1 = require("./ViewState");
const internal_1 = require("./tile/internal");
/** Defines a view of one or more SpatialModels.
 * The list of viewed models is stored in the ModelSelector.
 * @public
 * @extensions
 */
class SpatialViewState extends ViewState_1.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 core_bentley_1.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_1.CategorySelectorState(blank, iModel);
        const modelSelectorState = new ModelSelectorState_1.ModelSelectorState(blank, iModel);
        const displayStyleState = new DisplayStyleState_1.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_1.CategorySelectorState(props.categorySelectorProps, iModel);
        const displayStyleState = new DisplayStyleState_1.DisplayStyle3dState(props.displayStyleProps, iModel);
        const modelSelectorState = new ModelSelectorState_1.ModelSelectorState((0, core_bentley_1.expectDefined)(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 = internal_1.SpatialTileTreeReferences.create(this);
    }
    isSpatialView() { return true; }
    equals(other) { return super.equals(other) && this.modelSelector.equals(other.modelSelector); }
    createAuxCoordSystem(acsName) { return AuxCoordSys_1.AuxCoordSystemSpatialState.createNew(acsName, this.iModel); }
    get defaultExtentLimits() { return { min: core_geometry_1.Constant.oneMillimeter, max: 3 * core_geometry_1.Constant.diameterOfEarth }; } // Increased max by 3X to support globe mode.
    /** @internal */
    markModelSelectorChanged() {
        this._treeRefs.update();
    }
    computeBaseExtents() {
        const extents = core_geometry_1.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 core_geometry_1.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.modelSelector = { id: this.modelSelector.id };
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        val.modelSelectorId = this.modelSelector.id; // for backward compatibility
        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 = core_bentley_1.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;
    }
}
exports.SpatialViewState = SpatialViewState;
/** Defines a spatial view that displays geometry on the image plane using a parallel orthographic projection.
 * @public
 * @extensions
 */
class OrthographicViewState extends SpatialViewState {
    static get className() { return "OrthographicViewDefinition"; }
    constructor(props, iModel, categories, displayStyle, modelSelector) { super(props, iModel, categories, displayStyle, modelSelector); }
    supportsCamera() { return false; }
}
exports.OrthographicViewState = OrthographicViewState;
//# sourceMappingURL=SpatialViewState.js.map