/*---------------------------------------------------------------------------------------------
* 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
 */
// cspell:ignore ovrs
import { JsonUtils } from "@itwin/core-bentley";
/** Enumerates the available basic rendering modes, as part of a [DisplayStyle]($backend)'s [[ViewFlags]].
 * The rendering mode broadly affects various aspects of the display style - in particular, whether and how surfaces and their edges are drawn.
 * @public
 * @extensions
 */
export var RenderMode;
(function (RenderMode) {
    /** Renders only the edges of surfaces, with exceptions for planar regions based on their [[FillFlags]].
     * Lighting (and by extension, shadows) is not applied.
     * [[HiddenLine.Settings]] are not applied - edges use the elements' width, style, and color.
     * [[ViewFlags.hiddenEdges]] is ignored - hidden edges are never displayed in wireframe mode.
     */
    RenderMode[RenderMode["Wireframe"] = 0] = "Wireframe";
    /** By default, renders surfaces without their edges.
     * Lighting and shadows can be applied using [[ViewFlags.lighting]] and [[ViewFlags.shadows]].
     * Edges can be enabled using [[ViewFlags.visibleEdges]] and [[ViewFlags.hiddenEdges]], and their appearance customized using [[HiddenLine.Settings]].
     * Surfaces can be drawn with transparency, based on [[ViewFlags.transparency]].
     */
    RenderMode[RenderMode["SmoothShade"] = 6] = "SmoothShade";
    /** Renders surfaces and their edges. By default, edges are drawn in white; this can be overridden using [[HiddenLine.Settings]].
     * All surfaces are rendered opaque. If a surface's transparency is below that specified by [[HiddenLine.Settings.transparencyThreshold]], it is not rendered.
     * Materials and textures are not applied - surfaces are drawn in their actual colors.
     * [[ViewFlags.visibleEdges]] is ignored - visible edges are always drawn. Hidden edges can be enabled using [[ViewFlags.hiddenEdges]].
     * Lighting (and by extension, shadows) is not applied.
     */
    RenderMode[RenderMode["SolidFill"] = 4] = "SolidFill";
    /** Identical to [[RenderMode.SolidFill]], except:
     *  - Surfaces are drawn using the [DisplayStyle]($backend)'s background color.
     *  - Edges are drawn using their surface's colors; this can be overridden using [[HiddenLine.Settings]].
     */
    RenderMode[RenderMode["HiddenLine"] = 3] = "HiddenLine";
})(RenderMode || (RenderMode = {}));
function edgesRequired(renderMode, visibleEdges) {
    return visibleEdges || RenderMode.SmoothShade !== renderMode;
}
/** Flags controlling how graphics appear within a view.
 * A [[ViewFlags]] object is immutable. There are several ways to produce a modified copy of a ViewFlags object:
 * ```ts
 *  // Start with the default values for all properties.
 *  let vf = ViewFlags.defaults;
 *  // Change a single boolean property:
 *  vf = vf.with("visibleEdges", true);
 *  // Change only the render mode:
 *  vf = vf.withRenderMode(RenderMode.HiddenLine);
 *  // Change multiple properties:
 *  vf = vf.copy({ renderMode: RenderMode.SmoothShade, visibleEdges: true });
 *  // Reset multiple properties to their default values:
 *  vf = vf.copy({ renderMode: undefined, visibleEdges: undefined });
 *
 * ```
 * [[with]] and [[withRenderMode]] should be preferred if you only need to change a single property, as they will not create a new object unless
 * the new value differs from the current value.
 * [[copy]] and [[override]] should be preferred if you need to change multiple properties, as they will create no more than one new object, vs
 * each call to [[with]] or [[withRenderMode]] potentially creating a new object.
 * @see [[DisplayStyleSettings.viewFlags]] to define the view flags for a [DisplayStyle]($backend).
 * @public
 */
export class ViewFlags {
    /** Create a new ViewFlags.
     * @param flags The properties to initialize. Any properties not specified are initialized to their default values.
     */
    constructor(flags) {
        this.renderMode = flags?.renderMode ?? RenderMode.Wireframe;
        this.dimensions = flags?.dimensions ?? true;
        this.patterns = flags?.patterns ?? true;
        this.weights = flags?.weights ?? true;
        this.styles = flags?.styles ?? true;
        this.transparency = flags?.transparency ?? true;
        this.fill = flags?.fill ?? true;
        this.textures = flags?.textures ?? true;
        this.materials = flags?.materials ?? true;
        this.acsTriad = flags?.acsTriad ?? false;
        this.grid = flags?.grid ?? false;
        this.visibleEdges = flags?.visibleEdges ?? false;
        this.hiddenEdges = flags?.hiddenEdges ?? false;
        this.shadows = flags?.shadows ?? false;
        this.clipVolume = flags?.clipVolume ?? true;
        this.constructions = flags?.constructions ?? false;
        this.monochrome = flags?.monochrome ?? false;
        this.backgroundMap = flags?.backgroundMap ?? false;
        this.ambientOcclusion = flags?.ambientOcclusion ?? false;
        this.thematicDisplay = flags?.thematicDisplay ?? false;
        this.wiremesh = flags?.wiremesh ?? false;
        this.forceSurfaceDiscard = flags?.forceSurfaceDiscard ?? false;
        this.whiteOnWhiteReversal = flags?.whiteOnWhiteReversal ?? true;
        this.lighting = flags?.lighting ?? false;
    }
    /** Produce a copy of these ViewFlags with some modified properties. Any properties not explicitly specified by `changedFlags` will retain their current values.
     * @param changedFlags Properties to modify.
     * @returns A copy of these ViewFlags modified according to the supplied properties.
     * @note Any explicitly `undefined` property of `changedFlags` will be set to its default value in the returned ViewFlags.
     * @see [[override]] to have `undefined` properties retain their current values.
     */
    copy(changedFlags) {
        return JsonUtils.isNonEmptyObject(changedFlags) ? new ViewFlags({ ...this, ...changedFlags }) : this;
    }
    /** Produce a copy of these ViewFlags, overriding some of its properties. Any properties not explicitly specified by `overrides` will retain their current values,
     * as will any property explicitly set to `undefined`.
     * @param overrides The properties to override.
     * @see [[copy]] to have `undefined` properties reset to their default values.
     */
    override(overrides) {
        // This method can get called very frequently when a RenderTimeline script is applied to the view. Often `overrides` will be an empty object.
        // To optimize:
        //  - Bail as quickly as possible if nothing is actually overridden, without allocating a new ViewFlags.
        //  - Only make a copy of the input if at least one property is explicitly `undefined`.
        let copied = false;
        let anyOverridden = false;
        for (const propName of Object.keys(overrides)) {
            const key = propName;
            const overrideValue = overrides[key];
            if (undefined === overrideValue) {
                if (!copied) {
                    // Don't modify input...
                    overrides = { ...overrides };
                    copied = true;
                }
                // `undefined` means "retain existing value".
                delete overrides[key];
            }
            else if (overrideValue !== this[key]) {
                anyOverridden = true;
            }
        }
        return anyOverridden ? this.copy(overrides) : this;
    }
    /** Produce a copy of these ViewFlags with a single boolean property changed.
     * @param flag The name of the property.
     * @param value The value to change the property to.
     * @returns A new ViewFlags with the property changed as specified, or `this` if the property already has the specified value.
     * @see [[withRenderMode]] to change the [[renderMode]] property.
     * @see [[copy]] and [[override]] to change multiple properties.
     */
    with(flag, value) {
        if (this[flag] === value)
            return this;
        const props = { ...this };
        props[flag] = value;
        return new ViewFlags(props);
    }
    /** Produce a copy of these ViewFlags with a different [[renderMode]].
     * @param renderMode The new render mode.
     * @returns A new ViewFlags with the render mode changed as specified, or `this` if the render mode is already set to the requested value.
     * @see [[copy]] and [[override]] to change multiple properties.
     */
    withRenderMode(renderMode) {
        return renderMode === this.renderMode ? this : this.copy({ renderMode });
    }
    /** Adjust view flags for renderer.
     * @internal
     */
    normalize() {
        switch (this.renderMode) {
            case RenderMode.Wireframe:
                if (this.visibleEdges || this.hiddenEdges)
                    return this.copy({ visibleEdges: false, hiddenEdges: false });
                break;
            case RenderMode.SmoothShade:
                if (!this.visibleEdges)
                    return this.copy({ hiddenEdges: false });
                break;
            case RenderMode.HiddenLine:
            case RenderMode.SolidFill:
                if (!this.visibleEdges || this.transparency)
                    return this.copy({ visibleEdges: true, transparency: false });
                break;
        }
        return this;
    }
    /** Returns true if edges that could be occluded by other geometry are visible for the current [[RenderMode]]. */
    hiddenEdgesVisible() {
        switch (this.renderMode) {
            case RenderMode.SolidFill:
            case RenderMode.HiddenLine:
                return this.hiddenEdges;
            case RenderMode.SmoothShade:
                return this.visibleEdges && this.hiddenEdges;
        }
        return true;
    }
    /** Returns true if the edges of surfaces should be displayed, based on [[RenderMode]] and the [[visibleEdges]] flag. */
    edgesRequired() {
        return edgesRequired(this.renderMode, this.visibleEdges);
    }
    /** Convert to JSON representation.
     * Properties are omitted if they match the default values.
     */
    toJSON() {
        const out = {};
        if (!this.constructions)
            out.noConstruct = true;
        if (!this.dimensions)
            out.noDim = true;
        if (!this.patterns)
            out.noPattern = true;
        if (!this.weights)
            out.noWeight = true;
        if (!this.styles)
            out.noStyle = true;
        if (!this.transparency)
            out.noTransp = true;
        if (!this.fill)
            out.noFill = true;
        if (this.grid)
            out.grid = true;
        if (this.acsTriad)
            out.acs = true;
        if (!this.textures)
            out.noTexture = true;
        if (!this.materials)
            out.noMaterial = true;
        if (!this.lighting)
            out.noCameraLights = out.noSourceLights = out.noSolarLight = true;
        if (this.visibleEdges)
            out.visEdges = true;
        if (this.hiddenEdges)
            out.hidEdges = true;
        if (this.shadows)
            out.shadows = true;
        if (this.clipVolume)
            out.clipVol = true;
        if (this.monochrome)
            out.monochrome = true;
        if (this.backgroundMap)
            out.backgroundMap = true;
        if (this.ambientOcclusion)
            out.ambientOcclusion = true;
        if (this.thematicDisplay)
            out.thematicDisplay = true;
        if (this.wiremesh)
            out.wiremesh = true;
        if (this.forceSurfaceDiscard)
            out.forceSurfaceDiscard = true;
        if (!this.whiteOnWhiteReversal)
            out.noWhiteOnWhiteReversal = true;
        out.renderMode = this.renderMode;
        return out;
    }
    /** Like [[toJSON]], but no properties are omitted. */
    toFullyDefinedJSON() {
        return {
            renderMode: this.renderMode,
            noConstruct: !this.constructions,
            noDim: !this.dimensions,
            noPattern: !this.patterns,
            noWeight: !this.weights,
            noStyle: !this.styles,
            noTransp: !this.transparency,
            noFill: !this.fill,
            grid: this.grid,
            acs: this.acsTriad,
            noTexture: !this.textures,
            noMaterial: !this.materials,
            noCameraLights: !this.lighting,
            noSourceLights: !this.lighting,
            noSolarLight: !this.lighting,
            visEdges: this.visibleEdges,
            hidEdges: this.hiddenEdges,
            shadows: this.shadows,
            clipVol: this.clipVolume,
            monochrome: this.monochrome,
            backgroundMap: this.backgroundMap,
            ambientOcclusion: this.ambientOcclusion,
            thematicDisplay: this.thematicDisplay,
            wiremesh: this.wiremesh,
            forceSurfaceDiscard: this.forceSurfaceDiscard,
            noWhiteOnWhiteReversal: !this.whiteOnWhiteReversal,
        };
    }
    /** Create a ViewFlags.
     * @param flags The properties to initialize. Any properties not specified are initialized to their default values.
     */
    static create(flags) {
        return flags && !JsonUtils.isEmptyObject(flags) ? new ViewFlags(flags) : this.defaults;
    }
    /** Create a ViewFlags from its JSON representation.
     * @note As described in [[ViewFlagProps]], the JSON representation is awkward and error-prone. Prefer to use [[create]] unless you
     * need to deal with the persistence format directly.
     * @note The default values differ slightly from those used by the constructor and [[create]]:
     *  - [[clipVolume]] defaults to false.
     *  - [[constructions]] defaults to true.
     *  - [[lighting]] defaults to true unless all of [[ViewFlagProps.noSolarLight]], [[ViewFlagProps.noCameraLights]], and [[ViewFlagProps.noSourceLights]] are true.
     */
    static fromJSON(json) {
        if (!json)
            return this.defaults;
        let renderMode;
        const renderModeValue = JsonUtils.asInt(json.renderMode);
        if (renderModeValue < RenderMode.HiddenLine)
            renderMode = RenderMode.Wireframe;
        else if (renderModeValue > RenderMode.SolidFill)
            renderMode = RenderMode.SmoothShade;
        else
            renderMode = renderModeValue;
        const lighting = !JsonUtils.asBool(json.noCameraLights) || !JsonUtils.asBool(json.noSourceLights) || !JsonUtils.asBool(json.noSolarLight);
        return new ViewFlags({
            renderMode,
            lighting,
            constructions: !JsonUtils.asBool(json.noConstruct),
            dimensions: !JsonUtils.asBool(json.noDim),
            patterns: !JsonUtils.asBool(json.noPattern),
            weights: !JsonUtils.asBool(json.noWeight),
            styles: !JsonUtils.asBool(json.noStyle),
            transparency: !JsonUtils.asBool(json.noTransp),
            fill: !JsonUtils.asBool(json.noFill),
            grid: JsonUtils.asBool(json.grid),
            acsTriad: JsonUtils.asBool(json.acs),
            textures: !JsonUtils.asBool(json.noTexture),
            materials: !JsonUtils.asBool(json.noMaterial),
            visibleEdges: JsonUtils.asBool(json.visEdges),
            hiddenEdges: JsonUtils.asBool(json.hidEdges),
            shadows: JsonUtils.asBool(json.shadows),
            clipVolume: JsonUtils.asBool(json.clipVol),
            monochrome: JsonUtils.asBool(json.monochrome),
            backgroundMap: JsonUtils.asBool(json.backgroundMap),
            ambientOcclusion: JsonUtils.asBool(json.ambientOcclusion),
            thematicDisplay: JsonUtils.asBool(json.thematicDisplay),
            wiremesh: JsonUtils.asBool(json.wiremesh),
            forceSurfaceDiscard: JsonUtils.asBool(json.forceSurfaceDiscard),
            whiteOnWhiteReversal: !JsonUtils.asBool(json.noWhiteOnWhiteReversal),
        });
    }
    /** Returns true if `this` and `other` are equivalent. */
    equals(other) {
        if (this === other)
            return true;
        return this.renderMode === other.renderMode
            && this.dimensions === other.dimensions
            && this.patterns === other.patterns
            && this.weights === other.weights
            && this.styles === other.styles
            && this.transparency === other.transparency
            && this.fill === other.fill
            && this.textures === other.textures
            && this.materials === other.materials
            && this.acsTriad === other.acsTriad
            && this.grid === other.grid
            && this.visibleEdges === other.visibleEdges
            && this.hiddenEdges === other.hiddenEdges
            && this.lighting === other.lighting
            && this.shadows === other.shadows
            && this.clipVolume === other.clipVolume
            && this.constructions === other.constructions
            && this.monochrome === other.monochrome
            && this.backgroundMap === other.backgroundMap
            && this.ambientOcclusion === other.ambientOcclusion
            && this.thematicDisplay === other.thematicDisplay
            && this.wiremesh === other.wiremesh
            && this.forceSurfaceDiscard === other.forceSurfaceDiscard
            && this.whiteOnWhiteReversal === other.whiteOnWhiteReversal;
    }
}
/** A ViewFlags object with all properties initialized to their default values. */
ViewFlags.defaults = new ViewFlags();
//# sourceMappingURL=ViewFlags.js.map