light_LightSource.ts

import {Vec3} from "../math/Vec3";
import {RenderNode} from "../scene/RenderNode";

export interface ILightSourceParams {
    position?: Vec3;
    directional?: boolean;
    ambient?: Vec3;
    diffuse?: Vec3;
    specular?: Vec3;
    shininess?: number;
}

/**
 * Represents basic light source.
 * @class
 * @param {string} [name] - Light source name.
 * @param {ILightSourceParams} [params] - Light parameters:
 * @param {Vec3} [params.position] - Light source position if it is a point light, otherwise it is a light direction vector.
 * @param {Vec3} [params.ambient]  - Ambient RGB color.
 * @param {Vec3} [params.diffuse]  - Diffuse RGB color.
 * @param {Vec3} [params.specular]  - Specular RGB color.
 * @param {number} [params.shininess]  - Specular shininess.
 */
class LightSource {

    static __counter__: number = 0;

    /**
     * Light name.
     * @protected
     * @type {string}
     */
    protected _name: string;

    /**
     * Render node where light is shines.
     * @protected
     * @type {RenderNode}
     */
    protected _renderNode: RenderNode | null;

    /**
     * Light position.
     * @public
     * @type {Vec3}
     */
    public _position: Vec3;

    /**
     * True if the light is directional.
     * @public
     * @type {boolean}
     */
    public directional: boolean;

    /**
     * Ambient color.
     * @protected
     * @type {Vec3}
     */
    protected _ambient: Vec3;

    /**
     * Diffuse color.
     * @protected
     * @type {Vec3}
     */
    protected _diffuse: Vec3;

    /**
     * Specular color.
     * @protected
     * @type {Vec3}
     */
    protected _specular: Vec3;

    /**
     * Shininess.
     * @protected
     * @type {number}
     */
    protected _shininess: number;

    /**
     * Light activity.
     * @protected
     * @type {boolean}
     */
    protected _active: boolean;

    protected _tempAmbient: Vec3;
    protected _tempDiffuse: Vec3;
    protected _tempSpecular: Vec3;
    protected _tempShininess: number;

    constructor(name: string, params: ILightSourceParams) {

        /**
         * Light name.
         * @protected
         * @type {string}
         */
        this._name = name || "light_" + LightSource.__counter__++;

        /**
         * Render node where light is shines.
         * @protected
         * @type {RenderNode}
         */
        this._renderNode = null;

        /**
         * Light position.
         * @public
         * @type {Vec3}
         */
        this._position = params.position || new Vec3();

        /**
         * True if the light is directional.
         * @public
         * @type {boolean}
         */
        this.directional = params.directional != undefined ? params.directional : true;

        /**
         * Ambient color.
         * @protected
         * @type {Vec3}
         */
        this._ambient = params.ambient || new Vec3();

        /**
         * Diffuse color.
         * @protected
         * @type {Vec3}
         */
        this._diffuse = params.diffuse || new Vec3(0.8, 0.8, 0.8);

        /**
         * Specular color.
         * @protected
         * @type {Vec3}
         */
        this._specular = params.specular || new Vec3(0.18, 0.18, 0.18);

        /**
         * Shininess.
         * @protected
         * @type {number}
         */
        this._shininess = params.shininess != undefined ? params.shininess : 3.3;

        /**
         * Light activity.
         * @protected
         * @type {boolean}
         */
        this._active = true;

        this._tempAmbient = this._ambient.clone();
        this._tempDiffuse = this._diffuse.clone();
        this._tempSpecular = this._specular.clone();
        this._tempShininess = this._shininess;
    }

    /**
     * Creates clone of the current light object.
     * @todo: TODO
     * @public
     * @returns {LightSource}
     */
    public clone() {
        // TODO
    }

    /**
     * Set light activity. If activity is false the light doesn't shine.
     * @public
     * @param {boolean} active - Light activity.
     */
    public setActive(active: boolean) {
        if (active && !this._active) {
            const rn = this._renderNode;
            if (rn) {
                let index = rn._lightsNames.indexOf(this._name);
                this._shininess = rn._lightsParamsf[index] = this._tempShininess;
                if (index != -1) {
                    index *= 9;
                    this._ambient.x = rn._lightsParamsv[index] = this._tempAmbient.x;
                    this._ambient.y = rn._lightsParamsv[index + 1] = this._tempAmbient.y;
                    this._ambient.z = rn._lightsParamsv[index + 2] = this._tempAmbient.z;

                    this._diffuse.x = rn._lightsParamsv[index + 3] = this._tempDiffuse.x;
                    this._diffuse.y = rn._lightsParamsv[index + 4] = this._tempDiffuse.y;
                    this._diffuse.z = rn._lightsParamsv[index + 5] = this._tempDiffuse.z;

                    this._specular.x = rn._lightsParamsv[index + 6] = this._tempSpecular.x;
                    this._specular.y = rn._lightsParamsv[index + 7] = this._tempSpecular.y;
                    this._specular.z = rn._lightsParamsv[index + 8] = this._tempSpecular.z;
                }
            }
            this._active = true;
        } else if (!active && this._active) {
            this._tempAmbient = this._ambient.clone();
            this._tempDiffuse = this._diffuse.clone();
            this._tempSpecular = this._specular.clone();
            this._tempShininess = this._shininess;
            this.setBlack();
            this._active = false;
        }
    }

    /**
     * Gets light activity.
     * @public
     * @returns {boolean}
     */
    public isActive(): boolean {
        return this._active;
    }

    /**
     * Set light source position, or if it is a directional type sets light direction vector.
     * @public
     * @param {Vec3} position - Light position or direction vector.
     */
    public setPosition3v(position: Vec3) {
        this._position.x = position.x;
        this._position.y = position.y;
        this._position.z = position.z;
    }

    /**
     * Set light source position, or if it is a directional type sets light direction vector.
     * @public
     */
    public setPosition(x: number, y: number, z: number) {
        this._position.x = x;
        this._position.y = y;
        this._position.z = z;
    }

    /**
     * Returns light source position, or if it is a directional type sets light direction vector.
     * @public
     * @returns {Vec3} - Light source position/direction.
     */
    public getPosition(): Vec3 {
        return this._position.clone();
    }

    /**
     * Set ambient color.
     * @public
     * @param {Vec3} rgb - Ambient color.
     */
    public setAmbient3v(rgb: Vec3) {
        this.setAmbient(rgb.x, rgb.y, rgb.z);
    }

    /**
     * Set diffuse color.
     * @public
     * @param {Vec3} rgb - Diffuse color.
     */
    public setDiffuse3v(rgb: Vec3) {
        this.setDiffuse(rgb.x, rgb.y, rgb.z);
    }

    /**
     * Set specular color.
     * @public
     * @param {Vec3} rgb - Specular color.
     */
    public setSpecular3v(rgb: Vec3) {
        this.setSpecular(rgb.x, rgb.y, rgb.z);
    }

    /**
     * Set ambient color.
     * @public
     */
    public setAmbient(r: number, g: number, b: number) {
        this._ambient.set(r, g, b);
        const rn = this._renderNode;
        if (rn) {
            let index = 9 * rn._lightsNames.indexOf(this._name);
            if (index != -1) {
                rn._lightsParamsv[index] = r;
                rn._lightsParamsv[index + 1] = g;
                rn._lightsParamsv[index + 2] = b;
            }
        }
    }

    /**
     * Set diffuse color.
     * @public
     */
    public setDiffuse(r: number, g: number, b: number) {
        this._diffuse.set(r, g, b);
        const rn = this._renderNode;
        if (rn) {
            let index = 9 * rn._lightsNames.indexOf(this._name);
            if (index != -1) {
                rn._lightsParamsv[index + 3] = r;
                rn._lightsParamsv[index + 4] = g;
                rn._lightsParamsv[index + 5] = b;
            }
        }
    }

    /**
     * Set specular color.
     * @public
     */
    public setSpecular(r: number, g: number, b: number) {
        this._specular.set(r, g, b);
        const rn = this._renderNode;
        if (rn) {
            let index = 9 * rn._lightsNames.indexOf(this._name);
            if (index != -1) {
                rn._lightsParamsv[index + 6] = r;
                rn._lightsParamsv[index + 7] = g;
                rn._lightsParamsv[index + 8] = b;
            }
        }
    }

    /**
     * Set material shininess.
     * @public
     */
    public setShininess(shininess: number) {
        this._shininess = shininess;
        const rn = this._renderNode;
        if (rn) {
            let index = rn._lightsNames.indexOf(this._name);
            if (index != -1) {
                rn._lightsParamsf[index] = shininess;
            }
        }
    }

    /**
     * Sets light to black.
     * @public
     */
    public setBlack() {
        this._ambient.clear();
        this._diffuse.clear();
        this._specular.clear();
        this._shininess = 0;
        const rn = this._renderNode;
        if (rn) {
            let index = 9 * rn._lightsNames.indexOf(this._name);
            if (index !== -1) {
                rn._lightsParamsv[index] = rn._lightsParamsv[index + 1] = rn._lightsParamsv[index + 2] =
                    rn._lightsParamsv[index + 3] = rn._lightsParamsv[index + 4] = rn._lightsParamsv[index + 5] =
                        rn._lightsParamsv[index + 6] = rn._lightsParamsv[index + 7] = rn._lightsParamsv[index + 8] = 0;
            }
        }
    }

    /**
     * Adds current light to the render node scene.
     * @public
     * @param {RenderNode} renderNode - Render node scene.
     */
    public addTo(renderNode: RenderNode) {
        this._renderNode = renderNode;
        renderNode._lights.push(this);
        renderNode._lightsNames.push(this._name);
        renderNode._lightsParamsf.push(this._shininess);
        renderNode._lightsParamsv.push.apply(renderNode._lightsParamsv, this._ambient.toArray());
        renderNode._lightsParamsv.push.apply(renderNode._lightsParamsv, this._diffuse.toArray());
        renderNode._lightsParamsv.push.apply(renderNode._lightsParamsv, this._specular.toArray());
        renderNode.transformLights();
    }

    /**
     * Removes from render node scene.
     * @public
     */
    public remove() {
        const rn = this._renderNode;
        if (rn) {
            // let li = rn.getLightById(this._name);
            // if (li != -1) {
            //     rn._lights.splice(li, 1);
            //     rn._lightsNames.splice(li, 1);
            //     rn._lightsParamsf.splice(li, 1);
            //     rn._lightsParamsv.splice(li, 9);
            // }
        }
        this._renderNode = null;
    }
}

export {LightSource};