"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 Rendering
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SceneContext = exports.DecorateContext = exports.DynamicsContext = exports.RenderContext = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const DecorationsCache_1 = require("./DecorationsCache");
const IModelApp_1 = require("./IModelApp");
const Scene_1 = require("./render/Scene");
const internal_1 = require("./tile/internal");
const Viewport_1 = require("./Viewport");
const GraphicType_1 = require("./common/render/GraphicType");
/** Provides context for producing [[RenderGraphic]]s for drawing within a [[Viewport]].
 * @public
 * @extensions
 */
class RenderContext {
    constructor(vp, frustum) {
        this._viewport = vp;
        this.viewFlags = vp.viewFlags;
        this.frustum = frustum ? frustum : vp.getFrustum();
        this.frustumPlanes = core_common_1.FrustumPlanes.fromFrustum(this.frustum);
    }
    /** Given a point in world coordinates, determine approximately how many pixels it occupies on screen based on this context's frustum. */
    getPixelSizeAtPoint(inPoint) {
        return this.viewport.viewingSpace.getPixelSizeAtPoint(inPoint);
    }
    /** The [[Viewport]] associated with this context. */
    get viewport() {
        return this._viewport;
    }
    /** The [[RenderSystem]] being used to produce graphics for this context. */
    get renderSystem() {
        return this.target.renderSystem;
    }
    /** @internal */
    get target() { return this.viewport.target; }
    /** @internal */
    _createGraphicBuilder(options) {
        return this.target.createGraphicBuilder({ ...options, viewport: this.viewport });
    }
    /** Create a builder for creating a [[GraphicType.Scene]] [[RenderGraphic]] for rendering within this context's [[Viewport]].
     * @param transform the local-to-world transform in which the builder's geometry is to be defined.
     * @returns A builder for creating a [[GraphicType.Scene]] [[RenderGraphic]] for rendering within this context's [[Viewport]].
     */
    createSceneGraphicBuilder(transform) {
        return this._createGraphicBuilder({ type: GraphicType_1.GraphicType.Scene, placement: transform });
    }
    /** Create a graphic from a [[GraphicBranch]]. */
    createGraphicBranch(branch, location, opts) {
        return this.target.renderSystem.createGraphicBranch(branch, location, opts);
    }
    /** Create a [[RenderGraphic]] which groups a set of graphics into a node in a scene graph, applying to each a transform and optional clip volume and symbology overrides.
     * @param branch Contains the group of graphics and the symbology overrides.
     * @param location the local-to-world transform applied to the grouped graphics.
     * @returns A RenderGraphic suitable for drawing the scene graph node within this context's [[Viewport]].
     * @see [[RenderSystem.createBranch]]
     */
    createBranch(branch, location) { return this.createGraphicBranch(branch, location); }
    /** Given the size of a logical pixel in meters, convert it to the size of a physical pixel in meters, if [[RenderSystem.dpiAwareLOD]] is `true`.
     * Used when computing LOD for graphics.
     * @internal
     */
    adjustPixelSizeForLOD(cssPixelSize) {
        return this.viewport.target.adjustPixelSizeForLOD(cssPixelSize);
    }
}
exports.RenderContext = RenderContext;
/** Provides context for an [[InteractiveTool]] to display decorations representing its current state.
 * @see [[InteractiveTool.onDynamicFrame]]
 * @public
 */
class DynamicsContext extends RenderContext {
    /** Add a graphic to the list of dynamic graphics to be drawn in this context's [[Viewport]]. */
    addGraphic(graphic) {
        if (undefined === this._dynamics)
            this._dynamics = [];
        this._dynamics.push(graphic);
    }
    /** @internal */
    changeDynamics() {
        this.viewport.changeDynamics(this._dynamics);
    }
    /** Create a builder for producing a [[RenderGraphic]] appropriate for rendering within this context's [[Viewport]].
     * @param options Options describing how to create the builder.
     * @returns A builder that produces a [[RenderGraphic]].
     */
    createGraphic(options) {
        return this._createGraphicBuilder(options);
    }
}
exports.DynamicsContext = DynamicsContext;
/** Provides context for a [[ViewportDecorator]] to add [[Decorations]] to be rendered within a [[Viewport]].
 * @public
 * @extensions
 */
class DecorateContext extends RenderContext {
    /** The [[ScreenViewport]] in which this context's [[Decorations]] will be drawn. */
    get viewport() {
        return super.viewport;
    }
    /** @internal */
    constructor(vp, decorations, cache) {
        super(vp);
        this._decorations = decorations;
        this._cache = cache;
    }
    /** Create a new DecorateContext.
     * @param args Describes the inputs to the context.
     * @note Typically the [[ScreenViewport]] takes care of creating the context for you.
     * @public
     */
    static create(args) {
        return new DecorateContext(args.viewport, args.output, args.cache ?? new DecorationsCache_1.DecorationsCache());
    }
    /** Create a builder for creating a [[RenderGraphic]] of the specified type appropriate for rendering within this context's [[Viewport]].
     * @param type The type of builder to create.
     * @param transform the local-to-world transform in which the builder's geometry is to be defined.
     * @param id If the decoration is to be pickable, a unique identifier to associate with the resultant [[RenderGraphic]].
     * @returns A builder for creating a [[RenderGraphic]] of the specified type appropriate for rendering within this context's [[Viewport]].
     * @see [[IModelConnection.transientIds]] for obtaining an ID for a pickable decoration.
     * @see [[createGraphic]] for more options.
     */
    createGraphicBuilder(type, transform, id) {
        return this.createGraphic({ type, placement: transform, pickable: undefined !== id ? { id } : undefined });
    }
    /** Create a builder for producing a [[RenderGraphic]] appropriate for rendering within this context's [[Viewport]].
     * @param options Options describing how to create the builder.
     * @returns A builder that produces a [[RenderGraphic]].
     */
    createGraphic(options) {
        return this._createGraphicBuilder(options);
    }
    /** @internal */
    addFromDecorator(decorator) {
        (0, core_bentley_1.assert)(undefined === this._curCacheableDecorator);
        try {
            if (decorator.useCachedDecorations) {
                const cached = this._cache.get(decorator);
                if (cached) {
                    this.restoreCache(cached);
                    return;
                }
                this._curCacheableDecorator = decorator;
            }
            decorator.decorate(this);
        }
        finally {
            this._curCacheableDecorator = undefined;
        }
    }
    /** Restores decorations onto this context from the specified array of cached decorations. */
    restoreCache(cachedDecorations) {
        cachedDecorations.forEach((cachedDecoration) => {
            switch (cachedDecoration.type) {
                case "graphic":
                    this.addDecoration(cachedDecoration.graphicType, cachedDecoration.graphicOwner);
                    break;
                case "canvas":
                    this.addCanvasDecoration(cachedDecoration.canvasDecoration, cachedDecoration.atFront);
                    break;
                case "html":
                    this.addHtmlDecoration(cachedDecoration.htmlElement);
                    break;
            }
        });
    }
    _appendToCache(decoration) {
        (0, core_bentley_1.assert)(undefined !== this._curCacheableDecorator);
        this._cache.add(this._curCacheableDecorator, decoration);
    }
    /** Calls [[GraphicBuilder.finish]] on the supplied builder to obtain a [[RenderGraphic]], then adds the graphic to the appropriate list of
     * [[Decorations]].
     * @param builder The builder from which to extract the graphic.
     * @note The builder should not be used after calling this method.
     */
    addDecorationFromBuilder(builder) {
        this.addDecoration(builder.type, builder.finish());
    }
    /** Adds a graphic to the set of [[Decorations]] to be drawn in this context's [[ScreenViewport]].
     * @param The type of the graphic, which determines to which list of decorations it is added.
     * @param decoration The decoration graphic to add.
     * @note The type must match the type with which the [[RenderGraphic]]'s [[GraphicBuilder]] was constructed.
     * @see [[DecorateContext.addDecorationFromBuilder]] for a more convenient API.
     */
    addDecoration(type, decoration) {
        if (this._curCacheableDecorator) {
            const graphicOwner = this.target.renderSystem.createGraphicOwner(decoration);
            this._appendToCache({ type: "graphic", graphicOwner, graphicType: type });
            decoration = graphicOwner;
        }
        switch (type) {
            case GraphicType_1.GraphicType.Scene:
                if (undefined === this._decorations.normal)
                    this._decorations.normal = [];
                this._decorations.normal.push(decoration);
                break;
            case GraphicType_1.GraphicType.WorldDecoration:
                if (!this._decorations.world)
                    this._decorations.world = [];
                this._decorations.world.push(decoration);
                break;
            case GraphicType_1.GraphicType.WorldOverlay:
                if (!this._decorations.worldOverlay)
                    this._decorations.worldOverlay = [];
                this._decorations.worldOverlay.push(decoration);
                break;
            case GraphicType_1.GraphicType.ViewOverlay:
                if (!this._decorations.viewOverlay)
                    this._decorations.viewOverlay = [];
                this._decorations.viewOverlay.push(decoration);
                break;
            case GraphicType_1.GraphicType.ViewBackground:
                this.setViewBackground(decoration);
                break;
        }
    }
    /** Add a [[CanvasDecoration]] to be drawn in this context's [[ScreenViewport]]. */
    addCanvasDecoration(decoration, atFront = false) {
        if (this._curCacheableDecorator)
            this._appendToCache({ type: "canvas", canvasDecoration: decoration, atFront });
        if (undefined === this._decorations.canvasDecorations)
            this._decorations.canvasDecorations = [];
        const list = this._decorations.canvasDecorations;
        if (0 === list.length || true === atFront)
            list.push(decoration);
        else
            list.unshift(decoration);
    }
    /** Add an HTMLElement to be drawn as a decoration in this context's [[ScreenViewport]]. */
    addHtmlDecoration(decoration) {
        if (this._curCacheableDecorator)
            this._appendToCache({ type: "html", htmlElement: decoration });
        // an element decoration being added might already be on the decorationDiv, just marked for removal
        if (decoration[Viewport_1.ELEMENT_MARKED_FOR_REMOVAL]) {
            decoration[Viewport_1.ELEMENT_MARKED_FOR_REMOVAL] = false;
        }
        else if (decoration.parentElement !== this.viewport.decorationDiv) {
            this.viewport.decorationDiv.appendChild(decoration);
        }
    }
    /** @internal */
    drawStandardGrid(gridOrigin, rMatrix, spacing, gridsPerRef, _isoGrid = false, _fixedRepetitions) {
        const vp = this.viewport;
        if (vp.viewingGlobe)
            return;
        const color = vp.getContrastToBackgroundColor();
        const planarGrid = this.viewport.target.renderSystem.createPlanarGrid(vp.getFrustum(), { origin: gridOrigin, rMatrix, spacing, gridsPerRef, color });
        if (planarGrid) {
            this.addDecoration(GraphicType_1.GraphicType.WorldDecoration, planarGrid);
        }
    }
    /** Display skyBox graphic that encompasses entire scene and rotates with camera.
     * @see [[RenderSystem.createSkyBox]].
     */
    setSkyBox(graphic) {
        this._decorations.skyBox = graphic;
    }
    /** Set the graphic to be displayed behind all other geometry as the background of this context's [[ScreenViewport]]. */
    setViewBackground(graphic) {
        this._decorations.viewBackground = graphic;
    }
}
exports.DecorateContext = DecorateContext;
/** Context used to create the scene to be drawn in a [[Viewport]]. The scene consists of a set of [[RenderGraphic]]s produced by the
 * [[TileTree]]s visible within the viewport. Creating the scene may result in the enqueueing of requests for [[Tile]] content which
 * should be displayed in the viewport but are not yet loaded.
 * @public
 */
class SceneContext extends RenderContext {
    /** @internal */
    markChildrenLoading() {
        this._missingChildTiles = true;
    }
    /** @internal */
    get hasMissingTiles() {
        return this._missingChildTiles || this.missingTiles.size > 0;
    }
    constructor(vp, frustum) {
        super(vp, frustum);
        this._missingChildTiles = false;
        /** The graphics comprising the scene. */
        this.scene = new Scene_1.Scene();
        /** @internal */
        this.missingTiles = new Set();
        this._graphicType = internal_1.TileGraphicType.Scene;
    }
    /** The viewed volume containing the scene. */
    get viewingSpace() {
        return undefined !== this._viewingSpace ? this._viewingSpace : this.viewport.viewingSpace;
    }
    /** @internal */
    get graphicType() { return this._graphicType; }
    /** Add the specified graphic to the scene. */
    outputGraphic(graphic) {
        switch (this._graphicType) {
            case internal_1.TileGraphicType.BackgroundMap:
                this.backgroundGraphics.push(graphic);
                break;
            case internal_1.TileGraphicType.Overlay:
                this.overlayGraphics.push(graphic);
                break;
            default:
                this.graphics.push(graphic);
                break;
        }
    }
    /** Indicate that the specified tile is desired for the scene but is not yet ready. A request to load its contents will later be enqueued. */
    insertMissingTile(tile) {
        switch (tile.loadStatus) {
            case internal_1.TileLoadStatus.NotLoaded:
            case internal_1.TileLoadStatus.Queued:
            case internal_1.TileLoadStatus.Loading:
                this.missingTiles.add(tile);
                break;
        }
    }
    /** @internal */
    requestMissingTiles() {
        IModelApp_1.IModelApp.tileAdmin.requestTiles(this.viewport, this.missingTiles);
    }
    /** @internal */
    addPlanarClassifier(classifiedModelId, classifierTree, planarClipMask) {
        // Target may have the classifier from a previous frame; if not we must create one.
        let classifier = this.viewport.target.getPlanarClassifier(classifiedModelId);
        if (undefined === classifier)
            classifier = this.viewport.target.createPlanarClassifier(classifierTree?.activeClassifier);
        // Either way, we need to collect the graphics to draw for this frame, and record that we did so.
        if (undefined !== classifier) {
            this.planarClassifiers.set(classifiedModelId, classifier);
            classifier.setSource(classifierTree, planarClipMask);
        }
        return classifier;
    }
    /** @internal */
    getPlanarClassifierForModel(modelId) {
        return this.planarClassifiers.get(modelId);
    }
    /** @internal */
    addBackgroundDrapedModel(drapedTreeRef, _heightRange) {
        const drapedTree = drapedTreeRef.treeOwner.tileTree;
        if (undefined === drapedTree)
            return undefined;
        const id = drapedTree.modelId;
        let drape = this.getTextureDrapeForModel(id);
        if (undefined !== drape)
            return drape;
        drape = this.viewport.target.getTextureDrape(id);
        if (undefined === drape && this.viewport.backgroundDrapeMap)
            drape = this.viewport.target.renderSystem.createBackgroundMapDrape(drapedTreeRef, this.viewport.backgroundDrapeMap);
        if (undefined !== drape)
            this.textureDrapes.set(id, drape);
        return drape;
    }
    /** @internal */
    getTextureDrapeForModel(modelId) {
        return this.textureDrapes.get(modelId);
    }
    /** @internal */
    withGraphicType(type, func) {
        const prevType = this._graphicType;
        this._graphicType = type;
        func();
        this._graphicType = prevType;
    }
    /** The graphics in the scene that will be drawn with depth. */
    get graphics() { return this.scene.foreground; }
    /** The graphics that will be drawn behind everything else in the scene. */
    get backgroundGraphics() { return this.scene.background; }
    /** The graphics that will be drawn in front of everything else in the scene. */
    get overlayGraphics() { return this.scene.overlay; }
    /** @internal */
    get planarClassifiers() { return this.scene.planarClassifiers; }
    /** @internal */
    get textureDrapes() { return this.scene.textureDrapes; }
    /** @internal */
    setVolumeClassifier(classifier, modelId) {
        this.scene.volumeClassifier = { classifier, modelId };
    }
}
exports.SceneContext = SceneContext;
//# sourceMappingURL=ViewContext.js.map