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};