"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 DisplayStyles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.LightSettings = exports.FresnelSettings = exports.HemisphereLights = exports.AmbientLight = exports.SolarLight = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const RgbColor_1 = require("./RgbColor");
function extractIntensity(value, defaultValue) {
    const maxIntensity = 5;
    return typeof value === "number" ? Math.max(0, Math.min(maxIntensity, value)) : defaultValue;
}
const defaultSolarDirection = core_geometry_1.Vector3d.create(0.272166, 0.680414, 0.680414);
/** Describes the solar directional light associated with a [[LightSettings]].
 * @see [[SolarLightProps]].
 * @public
 */
class SolarLight {
    /** Direction of the light in world coordinates. Defaults to a vector looking down on the scene at a 45 degree angle mostly along the Y axis. */
    direction;
    /** Intensity of the light, typically in [0..1] but can range up to 5. Default: 1.0 */
    intensity;
    /** If true, the light will be applied even when shadows are turned off for the view.
     * If false, a roughly overhead light of the same intensity oriented in view space will be used instead.
     * Default: false.
     */
    alwaysEnabled;
    /** If defined, the time in UNIX milliseconds from which [[direction]] was calculated.
     * @see [[DisplayStyleSettings.setSunTime]] to compute the solar direction from a point in time.
     */
    timePoint;
    constructor(json) {
        json = json || {};
        this.intensity = extractIntensity(json.intensity, 1);
        this.alwaysEnabled = core_bentley_1.JsonUtils.asBool(json.alwaysEnabled);
        if (json.direction)
            this.direction = core_geometry_1.Vector3d.fromJSON(json.direction);
        else
            this.direction = defaultSolarDirection.clone();
        if (typeof json.timePoint === "number")
            this.timePoint = json.timePoint;
    }
    toJSON() {
        const direction = this.direction.isAlmostEqual(defaultSolarDirection) ? undefined : this.direction.toJSON();
        const intensity = this.intensity !== 1 ? this.intensity : undefined;
        const alwaysEnabled = this.alwaysEnabled ? true : undefined;
        const timePoint = this.timePoint;
        if (undefined === direction && undefined === intensity && undefined === alwaysEnabled && undefined === timePoint)
            return undefined;
        const json = {};
        if (direction)
            json.direction = direction;
        if (undefined !== intensity)
            json.intensity = intensity;
        if (undefined !== alwaysEnabled)
            json.alwaysEnabled = alwaysEnabled;
        if (undefined !== timePoint)
            json.timePoint = timePoint;
        return json;
    }
    /** Create a copy of this SolarLight, identical except in any properties explicitly specified by `changedProps`, with a possible exception for [[timePoint]].
     * If `this.timePoint` is defined and `changedProps` defines `direction` but **doesn't** define `timePoint`, the time point will only be preserved in the
     * copy if `changesProps.direction` is equal to `this.direction`.
     */
    clone(changedProps) {
        if (!changedProps)
            return this;
        const props = this.toJSON() ?? {};
        if (undefined !== changedProps.direction)
            props.direction = changedProps.direction;
        if (undefined !== changedProps.intensity)
            props.intensity = changedProps.intensity;
        if (undefined !== changedProps.alwaysEnabled)
            props.alwaysEnabled = changedProps.alwaysEnabled;
        if (undefined !== changedProps.timePoint)
            props.timePoint = changedProps.timePoint;
        // If our direction was computed from a time point and the caller only supplies a direction, invalidate the time point unless the input direction matches our direction.
        // If caller explicitly supplied a timePoint, trust it.
        if (undefined !== this.timePoint && undefined === changedProps.timePoint && undefined !== changedProps.direction) {
            const newDirection = core_geometry_1.Vector3d.fromJSON(changedProps.direction);
            if (!newDirection.isAlmostEqual(this.direction))
                props.timePoint = undefined;
        }
        return new SolarLight(props);
    }
    equals(rhs) {
        return this.intensity === rhs.intensity && this.alwaysEnabled === rhs.alwaysEnabled && this.direction.isExactEqual(rhs.direction) && this.timePoint === rhs.timePoint;
    }
}
exports.SolarLight = SolarLight;
/** Describes the ambient light associated with a [[LightSettings]].
 * @see [[AmbientLightProps]]
 * @public
 */
class AmbientLight {
    color;
    intensity;
    constructor(json) {
        json = json || {};
        this.intensity = extractIntensity(json.intensity, 0.2);
        this.color = json.color ? RgbColor_1.RgbColor.fromJSON(json.color) : new RgbColor_1.RgbColor(0, 0, 0);
    }
    toJSON() {
        const color = this.color.r !== 0 || this.color.g !== 0 || this.color.b !== 0 ? this.color.toJSON() : undefined;
        const intensity = 0.2 !== this.intensity ? this.intensity : undefined;
        if (undefined === color && undefined === intensity)
            return undefined;
        const json = {};
        if (color)
            json.color = color;
        if (undefined !== intensity)
            json.intensity = intensity;
        return json;
    }
    /** Create a copy of this light, identical except for any properties explicitly specified by `changed`. */
    clone(changed) {
        if (!changed)
            return this;
        const props = this.toJSON() ?? {};
        if (undefined !== changed.intensity)
            props.intensity = changed.intensity;
        if (undefined !== changed.color)
            props.color = changed.color;
        return new AmbientLight(props);
    }
    equals(rhs) {
        return this.intensity === rhs.intensity && this.color.equals(rhs.color);
    }
}
exports.AmbientLight = AmbientLight;
const defaultUpperHemisphereColor = new RgbColor_1.RgbColor(143, 205, 255);
const defaultLowerHemisphereColor = new RgbColor_1.RgbColor(120, 143, 125);
/** Describes a pair of hemisphere lights associated with a [[LightSettings]].
 * @see [[HemisphereLightsProps]]
 * @public
 */
class HemisphereLights {
    upperColor;
    lowerColor;
    intensity;
    constructor(json) {
        json = json || {};
        this.intensity = extractIntensity(json.intensity, 0);
        this.upperColor = json.upperColor ? RgbColor_1.RgbColor.fromJSON(json.upperColor) : defaultUpperHemisphereColor;
        this.lowerColor = json.lowerColor ? RgbColor_1.RgbColor.fromJSON(json.lowerColor) : defaultLowerHemisphereColor;
    }
    toJSON() {
        const upperColor = this.upperColor.equals(defaultUpperHemisphereColor) ? undefined : this.upperColor.toJSON();
        const lowerColor = this.lowerColor.equals(defaultLowerHemisphereColor) ? undefined : this.lowerColor.toJSON();
        const intensity = 0 === this.intensity ? undefined : this.intensity;
        if (undefined === upperColor && undefined === lowerColor && undefined === intensity)
            return undefined;
        const json = {};
        if (upperColor)
            json.upperColor = upperColor;
        if (lowerColor)
            json.lowerColor = lowerColor;
        if (undefined !== intensity)
            json.intensity = intensity;
        return json;
    }
    /** Create a copy of these lights, identical except for any properties explicitly specified by `changed`. */
    clone(changed) {
        if (!changed)
            return this;
        const props = this.toJSON() || {};
        if (undefined !== changed.upperColor)
            props.upperColor = changed.upperColor;
        if (undefined !== changed.lowerColor)
            props.lowerColor = changed.lowerColor;
        if (undefined !== changed.intensity)
            props.intensity = changed.intensity;
        return new HemisphereLights(props);
    }
    equals(rhs) {
        return this.intensity === rhs.intensity && this.upperColor.equals(rhs.upperColor) && this.lowerColor.equals(rhs.lowerColor);
    }
}
exports.HemisphereLights = HemisphereLights;
function clampIntensity(intensity = 0) {
    return Math.max(intensity, 0);
}
/** As part of a [[LightSettings]], describes how to apply a Fresnel effect to the contents of the view.
 * The "Fresnel effect" is based on the observation that the reflectivity of a surface varies based on the angle between the surface and
 * the viewer's line of sight. For example, a flat surface will appear more reflective when viewed at a glancing angle than it will when
 * viewed from above; and a sphere will appear more reflective around its edges than at its center.
 *
 * This principle can be used to improve photorealism, but the implementation provided here is intended to produce non-realistic but
 * aesthetically-pleasing results.
 * @see [[LightSettings.fresnel]].
 * @public
 */
class FresnelSettings {
    /** The strength of the effect in terms of how much brighter the surface becomes. The intensity at a given point on the surface is determined by
     * the angle between the viewer's line of sight and the vector from the viewer to that point. Maximum intensity is produced when those vectors are
     * perpendicular, and zero intensity is produced when those vectors are parallel (unless [[invert]] is `true`).
     *
     * A value of zero turns off the effect. Values less than zero are clamped to zero. Typical values range between 0 and 1.
     */
    intensity;
    /** If true, inverts the effect's [[intensity]] such that maximum intensity is produced when the viewer's line of sight is parallel to the vector between
     * the viewer and the point on the surface, and zero intensity is produced when the viewer's line of sight is perpendicular to that vector.
     */
    invert;
    constructor(intensity, invert) {
        (0, core_bentley_1.assert)(intensity >= 0);
        this.intensity = intensity;
        this.invert = invert;
    }
    static _defaults = new FresnelSettings(0, false);
    /** Create from JSON representation, using default values for any unspecified or `undefined` properties. */
    static fromJSON(props) {
        const intensity = clampIntensity(core_bentley_1.JsonUtils.asDouble(props?.intensity));
        const invert = core_bentley_1.JsonUtils.asBool(props?.invert);
        if (0 === intensity && !invert)
            return this._defaults;
        return new this(intensity, invert);
    }
    /** Create a new FresnelSettings.
     * @note Intensity values less than zero will be set to zero.
     */
    static create(intensity = 0, invert = false) {
        return this.fromJSON({ intensity, invert });
    }
    /** Convert to JSON representation.
     * @note If all settings match the default values, `undefined` will be returned.
     */
    toJSON() {
        if (0 === this.intensity && !this.invert)
            return undefined;
        const props = {};
        if (0 !== this.intensity)
            props.intensity = this.intensity;
        if (this.invert)
            props.invert = true;
        return props;
    }
    /** Create a copy of these settings, modified to match any properties explicitly specified by `changedProps`. */
    clone(changedProps) {
        if ((undefined === changedProps?.intensity || changedProps.intensity === this.intensity)
            && (undefined === changedProps?.invert || changedProps.invert === this.invert))
            return this;
        const intensity = changedProps?.intensity ?? this.intensity;
        const invert = changedProps?.invert ?? this.invert;
        return FresnelSettings.fromJSON({ intensity, invert });
    }
    /** Return true if these settings are equivalent to `rhs`. */
    equals(rhs) {
        return this === rhs || (this.intensity === rhs.intensity && this.invert === rhs.invert);
    }
}
exports.FresnelSettings = FresnelSettings;
/** Describes the lighting for a 3d scene, associated with a [[DisplayStyle3dSettings]] in turn associated with a [DisplayStyle3d]($backend) or [DisplayStyle3dState]($frontend).
 * @see [[LightSettingsProps]]
 * @public
 */
class LightSettings {
    solar;
    ambient;
    hemisphere;
    /** See [[LightSettingsProps.portrait]]. */
    portraitIntensity;
    specularIntensity;
    /** See [[LightSettingsProps.numCels]]. */
    numCels;
    fresnel;
    constructor(solar, ambient, hemisphere, portraitIntensity, specularIntensity, numCels, fresnel) {
        this.solar = solar;
        this.ambient = ambient;
        this.hemisphere = hemisphere;
        this.portraitIntensity = portraitIntensity;
        this.specularIntensity = specularIntensity;
        this.numCels = numCels;
        this.fresnel = fresnel;
    }
    static fromJSON(props) {
        const solar = new SolarLight(props?.solar);
        const ambient = new AmbientLight(props?.ambient);
        const hemisphere = new HemisphereLights(props?.hemisphere);
        const portraitIntensity = extractIntensity(props?.portrait?.intensity, 0.3);
        const specularIntensity = extractIntensity(props?.specularIntensity, 1.0);
        const numCels = core_bentley_1.JsonUtils.asInt(props?.numCels, 0);
        const fresnel = FresnelSettings.fromJSON(props?.fresnel);
        return new LightSettings(solar, ambient, hemisphere, portraitIntensity, specularIntensity, numCels, fresnel);
    }
    toJSON() {
        const solar = this.solar.toJSON();
        const ambient = this.ambient.toJSON();
        const hemisphere = this.hemisphere.toJSON();
        const portrait = 0.3 !== this.portraitIntensity ? { intensity: this.portraitIntensity } : undefined;
        const specularIntensity = 1 !== this.specularIntensity ? this.specularIntensity : undefined;
        const numCels = 0 !== this.numCels ? this.numCels : undefined;
        const fresnel = this.fresnel.toJSON();
        if (!solar && !ambient && !hemisphere && !portrait && undefined === specularIntensity && undefined === numCels && undefined === fresnel)
            return undefined;
        const json = {};
        if (solar)
            json.solar = solar;
        if (ambient)
            json.ambient = ambient;
        if (hemisphere)
            json.hemisphere = hemisphere;
        if (portrait)
            json.portrait = portrait;
        if (undefined !== specularIntensity)
            json.specularIntensity = specularIntensity;
        if (undefined !== numCels)
            json.numCels = numCels;
        if (fresnel)
            json.fresnel = fresnel;
        return json;
    }
    /** Create a copy of these light settings, identical except for any properties explicitly specified by `changed`.
     * Note that the solar, ambient, and hemisphere lights will also be cloned using their own `clone` methods - so for example, the following:
     * `  clone({ ambient: { intensity: 0.5 } })`
     * will overwrite the ambient light's intensity but preserve its current color, rather than replacing the color with the default color.
     */
    clone(changed) {
        if (!changed)
            return this;
        const solar = this.solar.clone(changed.solar);
        const ambient = this.ambient.clone(changed.ambient);
        const hemisphere = this.hemisphere.clone(changed.hemisphere);
        const portrait = changed.portrait?.intensity ?? this.portraitIntensity;
        const specular = changed.specularIntensity ?? this.specularIntensity;
        const numCels = changed.numCels ?? this.numCels;
        const fresnel = this.fresnel.clone(changed.fresnel);
        return new LightSettings(solar, ambient, hemisphere, portrait, specular, numCels, fresnel);
    }
    equals(rhs) {
        if (this === rhs)
            return true;
        return this.portraitIntensity === rhs.portraitIntensity && this.specularIntensity === rhs.specularIntensity && this.numCels === rhs.numCels
            && this.ambient.equals(rhs.ambient) && this.solar.equals(rhs.solar) && this.hemisphere.equals(rhs.hemisphere) && this.fresnel.equals(rhs.fresnel);
    }
}
exports.LightSettings = LightSettings;
//# sourceMappingURL=LightSettings.js.map