Source: og/scene/RenderNode.js

"use strict";

import { BaseNode } from "./BaseNode.js";
import { Events } from "../Events.js";

/**
 * Render node is a logical part of a render mechanism. Represents scene rendering.
 * Forexample one scene node for rendering the Earth, another one for rendering the Moon, another node for rendering stars etc.
 * Each render node has own model view space defined with matrices(scale, rotation, translation, transformation).
 * There are collections of ligh sources, entities and so on in the node.
 * Access to the node is renderer.renderNodes["Earth"]
 * @class
 * @extends {RenderNode}
 * @param {string} name - Node name.
 */
class RenderNode extends BaseNode {
    constructor(name) {
        super(name);

        /**
         * Renderer that calls frame() callback.
         * @public
         * @type {Renderer}
         */
        this.renderer = null;

        this.drawMode = null;

        /** Show rendering.
         * @public
         */
        this.show = true;

        this._isActive = true;

        /**
         * Lighting calculations.
         * @public
         * @type {boolean}
         */
        this.lightEnabled = false;

        /**
         * Point light array.
         * @private
         * @type {Array.<LightSource>}
         */
        this._lights = [];
        this._lightsTransformedPositions = [];
        this._lightsParamsv = [];
        this._lightsParamsf = [];
        this._lightsNames = [];

        /**
         * Entity collection array.
         * @public
         * @type {Array.<EntityCollection>}
         */
        this.entityCollections = [];

        this._pickingId = -1;

        this.events = new Events(null, this);
    }

    /**
     * Adds node to the current hierarchy.
     * @public
     * @type {RenderNode}
     */
    addNode(node) {
        super.addNode(node);
        node.assign(this.renderer);
    }

    /**
     * Assign render node with renderer.
     * @public
     * @param {Renderer} renderer - Redner node's renderer.
     */
    assign(renderer) {
        this.renderer = renderer;
        this._pickingId = renderer.addPickingCallback(this, this._entityCollectionPickingCallback);

        for (var i = 0; i < this.entityCollections.length; i++) {
            this.entityCollections[i].bindRenderNode(this);
        }

        this.init && this.init();
    }

    onremove() {
        //virtual
    }

    remove() {
        var r = this.renderer,
            n = this.name;

        if (r) {
            if (r.renderNodes[n] && r.renderNodes[n].isEqual(this)) {
                r.renderNodes[n] = null;
                delete r.renderNodes[n];
            }

            for (var i = 0; i < r._renderNodesArr.length; i++) {
                if (r._renderNodesArr[i].isEqual(this)) {
                    r._renderNodesArr.splice(i, 1);
                    break;
                }
            }
            r.removePickingCallback(this._pickingId);
            this._pickingId = -1;
            this.onremove && this.onremove();
        }
    }

    /**
     * Adds entity collection.
     * @public
     * @param {EntityCollection} entityCollection - Entity collection.
     * @param {boolean} [isHidden] - If it's true that this collection has specific rendering.
     * @returns {RenderNode} -
     */
    addEntityCollection(entityCollection, isHidden) {
        entityCollection.addTo(this, isHidden);
        return this;
    }

    /**
     * Removes entity collection.
     * @public
     * @param {EntityCollection} entityCollection - Entity collection for remove.
     */
    removeEntityCollection(entityCollection) {
        entityCollection.remove();
    }

    /**
     * Adds point light source.
     * @public
     * @param {LightSource} light - Light source.
     * @returns {RenderNode}
     */
    addLight(light) {
        light.addTo(this);
        return this;
    }

    /**
     * Gets light object by its name.
     * @public
     * @param {string} name - Point light name.
     * @returns {LightSource}
     */
    getLightByName(name) {
        var li = this._lightsNames.indexOf(name);
        return this._lights[li];
    }

    /**
     * Removes light source.
     * @public
     * @param {LightSource} light - Light source object.
     */
    removeLight(light) {
        light.remove();
    }

    /**
     * Calls render frame node's callback. Used in renderer.
     * @public
     */
    drawNode(frustum, frustumIndex) {
        this._isActive && this._drawNodes(frustum, frustumIndex);
    }

    /**
     * Gets render node activity.
     * @public
     * @returns {Boolean} -
     */
    isActive() {
        return this._isActive;
    }

    /**
     * Rendering activation.
     * @public
     * @param {boolean} isActive - Activation flag.
     */
    setActive(isActive) {
        this._isActive = isActive;

        if (this.renderer) {
            if (this._isActive && this._pickingId === -1) {
                this._pickingId = this.renderer.addPickingCallback(
                    this,
                    this._entityCollectionPickingCallback
                );
            } else if (!this._isActive && this._pickingId !== -1) {
                this.renderer.removePickingCallback(this._pickingId);
                this._pickingId = -1;
            }
        }

        for (var i = 0; i < this.childNodes.length; i++) {
            this.childNodes[i].setActive(isActive);
        }
    }

    /**
     * Sets draw mode
     * @public
     * @param {Number} mode - Draw mode, such as gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.LINES etc.
     */
    setDrawMode(mode) {
        this.drawMode = mode;
        for (var i = 0; i < this.childNodes.length; i++) {
            this.childNodes[i].setDrawMode(mode);
        }
    }

    /**
     * IMPORTANT: This function have to be called manualy in each render node frame callback, before drawing scene geometry.
     * @public
     */
    transformLights() {
        var r = this.renderer;
        for (var i = 0; i < this._lights.length; i++) {
            var ii = i * 4;
            var tp;
            if (this._lights[i].directional) {
                tp = r.activeCamera._normalMatrix.mulVec(this._lights[i]._position);
                this._lightsTransformedPositions[ii + 3] = 0;
            } else {
                tp = r.activeCamera._viewMatrix.mulVec3(this._lights[i]._position);
                this._lightsTransformedPositions[ii + 3] = 1;
            }
            this._lightsTransformedPositions[ii] = tp.x;
            this._lightsTransformedPositions[ii + 1] = tp.y;
            this._lightsTransformedPositions[ii + 2] = tp.z;
        }
    }

    updateBillboardsTexCoords() {
        for (var i = 0; i < this.entityCollections.length; i++) {
            this.entityCollections[i].billboardHandler.refreshTexCoordsArr();
        }
    }

    /**
     * @private
     */
    _drawNodes() {
        for (var i = 0; i < this.childNodes.length; i++) {
            if (this.childNodes[i]._isActive) {
                this.childNodes[i]._drawNodes();
            }
        }

        if (this.show) {
            if (this.frame) {
                //this.lightEnabled && this.transformLights();
                this.frame();
            }
            this.drawEntityCollections(this.entityCollections);
        }
    }

    drawEntityCollections(ec) {
        this.renderer.enqueueEntityCollectionsToDraw(ec);
    }

    /**
     * Draw entity collections picking frame.
     * @public
     * @param {Array<og.EntityCollection>} ec - Entity collection array.
     */
    drawPickingEntityCollections(ec) {
        if (ec.length) {
            var gl = this.renderer.handler.gl;

            gl.disable(gl.CULL_FACE);

            // Z-buffer offset
            gl.enable(gl.POLYGON_OFFSET_FILL);
            gl.polygonOffset(0.0, 0.0);

            // billoard pass
            var i = ec.length;
            while (i--) {
                ec[i]._fadingOpacity && ec[i].billboardHandler.drawPicking();
            }

            // geoObject pass
            i = ec.length;
            while (i--) {
                ec[i]._fadingOpacity && ec[i].geoObjectHandler.drawPicking();
            }

            // label pass
            i = ec.length;
            while (i--) {
                ec[i]._fadingOpacity && ec[i].labelHandler.drawPicking();
            }

            // ray pass
            i = ec.length;
            while (i--) {
                ec[i]._fadingOpacity && ec[i].rayHandler.drawPicking();
            }

            gl.polygonOffset(0.0, 0.0);

            gl.disable(gl.POLYGON_OFFSET_FILL);
            gl.enable(gl.CULL_FACE);

            // polylines pass
            i = ec.length;
            while (i--) {
                ec[i]._visibility && ec[i].polylineHandler.drawPicking();
            }

            //shapes pass
            i = ec.length;
            while (i--) {
                ec[i]._visibility && ec[i].shapeHandler.drawPicking();
            }

            //Strip pass
            i = ec.length;
            while (i--) {
                ec[i]._visibility && ec[i].stripHandler.drawPicking();
            }

            // //pointClouds pass
            // i = ec.length;
            // while (i--) {
            //    ec[i]._visibility && ec[i].pointCloudHandler.drawPicking();
            // }
        }
    }

    /**
     * Picking entity frame callback
     * @private
     */
    _entityCollectionPickingCallback() {
        this.drawPickingEntityCollections(this.entityCollections);
    }
}

export { RenderNode };