scene_RenderNode.ts

import {BaseNode} from "./BaseNode";
import {Renderer} from "../renderer/Renderer";
import {LightSource} from "../light/LightSource";
import {EntityCollection} from "../entity/EntityCollection";
import {Quat} from "../math/Quat";
import {Vec3} from "../math/Vec3";
import {Planet} from "./Planet";

/**
 * Render node is a logical part of a render mechanism. Represents scene rendering.
 * For example 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 light sources, entities and so on in the node.
 * Access to the node is renderer.renderNodes["Earth"]
 * @class
 * @extends {BaseNode}
 * @param {string} name - Node name.
 */

class RenderNode extends BaseNode {

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

    public drawMode: number;

    /** Show rendering.
     * @public
     */
    public show: boolean;

    protected _isActive: boolean;

    public override childNodes: RenderNode[];

    /**
     * Lighting calculations.
     * @public
     * @type {boolean}
     */
    public lightEnabled: boolean;

    /**
     * Point light array.
     * @public
     * @type {Array.<LightSource>}
     */
    public _lightPosition: Float32Array;
    public _lightParams: Float32Array;
    public _lightShininess: number;

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

    protected _entityCollectionsByDepthOrder: EntityCollection[][];

    protected _pickingId: number;

    constructor(name?: string) {
        super(name);

        this.childNodes = [];

        this.renderer = null;

        this.drawMode = 0;

        this.show = true;

        this._isActive = true;

        this.lightEnabled = false;

        this._lightPosition = new Float32Array([100, 100, 100]);
        this._lightParams = new Float32Array(9);
        this._lightShininess = 100.0;

        this.entityCollections = [];

        this._entityCollectionsByDepthOrder = [];

        this._pickingId = -1;
    }

    public getFrameRotation(cartesian: Vec3): Quat {
        return Quat.IDENTITY;
    }

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

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

    public initialize() {
        if (this.renderer && this.renderer.isInitialized()) {
            for (let i = 0; i < this.entityCollections.length; i++) {
                this.entityCollections[i].bindRenderNode(this);
            }
            this.init();
        }
    }

    public init() {
        //virtual
    }

    public onremove() {
        //virtual
    }

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

        if (r) {
            // TODO: replace to renderer
            if (r.renderNodes[n] && r.renderNodes[n].isEqual(this)) {
                // @ts-ignore
                r.renderNodes[n] = null;
                delete r.renderNodes[n];
            }

            for (let 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} -
     */
    public addEntityCollection(entityCollection: EntityCollection, isHidden?: boolean): void {

        if (!entityCollection.renderNode) {
            entityCollection.renderNode = this;

            if (!isHidden) {
                this.entityCollections.push(entityCollection);
                this.updateEntityCollectionsDepthOrder();
            }

            //@ts-ignore
            (this as Planet).ellipsoid && entityCollection._updateGeodeticCoordinates((this as Planet).ellipsoid);

            entityCollection.bindRenderNode(this);

            entityCollection.events.dispatch(entityCollection.events.add, this);
        }
    }

    /**
     * Removes entity collection.
     * @public
     * @param {EntityCollection} entityCollection - Entity collection for remove.
     */
    public removeEntityCollection(entityCollection: EntityCollection) {
        for (let i = 0; i < this.entityCollections.length; i++) {
            if (this.entityCollections[i].isEqual(entityCollection)) {
                this.entityCollections.splice(i, 1);
                this.updateEntityCollectionsDepthOrder();
                return;
            }
        }
    }

    public updateEntityCollectionsDepthOrder() {

        let grouped: Record<number, EntityCollection[]> = {0: []};
        for (const ec of this.entityCollections) {
            if (ec.getVisibility()) {
                if (!grouped[ec.depthOrder]) {
                    grouped[ec.depthOrder] = [];
                }
                grouped[ec.depthOrder].push(ec);
            }
        }

        this._entityCollectionsByDepthOrder.length = 0;
        this._entityCollectionsByDepthOrder = [];
        this._entityCollectionsByDepthOrder = Object.keys(grouped)
            .sort((a, b) => Number(a) - Number(b))
            .map(key => grouped[Number(key)]);
    }

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

    /**
     * Calls render frame node's callback. Used in renderer.
     * @public
     */
    public preDrawNode() {
        this._isActive && this._preDrawNodes();
    }


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

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

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

        if (this.renderer) {
            if (this._isActive && this._pickingId === -1) {
                // This picking callback MUST be the first picking callback
                // in the rendering queue in the renderer. It affects on blending.
                this._pickingId = this.renderer.addPickingCallback(
                    this,
                    this._entityCollectionPickingCallback
                );
            } else if (!this._isActive && this._pickingId !== -1) {
                this.renderer.removePickingCallback(this._pickingId);
                this._pickingId = -1;
            }
        }

        for (let 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.
     */
    public setDrawMode(mode: number) {
        this.drawMode = mode;
        for (let i = 0; i < this.childNodes.length; i++) {
            this.childNodes[i].setDrawMode(mode);
        }
    }

    // /**
    //  * IMPORTANT: This function have to be called manually in each render node frame callback, before drawing scene geometry.
    //  * @public
    //  */
    // public transformLights() {
    //     // for (let 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;
    //     // }
    // }

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

    // public updateGeoObjectsTexCoords() {
    //     for (let i = 0; i < this.entityCollections.length; i++) {
    //         this.entityCollections[i].geoObjectHandler.refreshTexCoordsArr();
    //     }
    // }

    public frame() {
        // virtual
    }

    public preFrame() {
        // virtual
    }

    protected _preDrawNodes() {
        for (let i = 0; i < this.childNodes.length; i++) {
            if (this.childNodes[i]._isActive) {
                this.childNodes[i]._preDrawNodes();
            }
        }

        if (this.show) {
            //this.lightEnabled && this.transformLights();
            this.preFrame();
            for (let i = 0; i < this._entityCollectionsByDepthOrder.length; i++) {
                this.drawEntityCollections(this._entityCollectionsByDepthOrder[i], i);
            }
        }
    }

    protected _drawNodes() {
        for (let i = 0; i < this.childNodes.length; i++) {
            if (this.childNodes[i]._isActive) {
                this.childNodes[i]._drawNodes();
            }
        }

        if (this.show) {
            this.frame();
        }
    }

    public drawEntityCollections(ec: EntityCollection[], depthOrder: number = 0) {
        this.renderer!.enqueueEntityCollectionsToDraw(ec, depthOrder);
    }

    /**
     * Draw entity collections picking frame.
     * @public
     * @param {Array<EntityCollection>} ec - Entity collection array.
     */
    public drawPickingEntityCollections(ec: EntityCollection[]) {
        if (ec.length) {

            // billboard pass
            let 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();
            }

            // polyline pass
            i = ec.length;
            while (i--) {
                ec[i]._visibility && ec[i].polylineHandler.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();
            // }
        }
    }

    protected _entityCollectionPickingCallback() {
        // for (let i = 0; i < this._entityCollectionsByDepthOrder.length; i++) {
        //     this.drawPickingEntityCollections(this._entityCollectionsByDepthOrder[i]);
        // }
    }
}

export {RenderNode};