scene_Planet.ts

import * as segmentHelper from "../segment/segmentHelper";
import * as shaders from "../shaders/drawnode/drawnode";
import * as utils from "../utils/shared";
import {Atmosphere} from "../control/atmosphere/Atmosphere";
import type {IAtmosphereParams} from "../control/atmosphere/Atmosphere";
import {Control} from "../control/Control";
import {createColorRGB} from "../utils/shared";
import {createEvents} from "../Events";
import type {EventsHandler} from "../Events";
import {EarthQuadTreeStrategy} from "../quadTree/earth/EarthQuadTreeStrategy";
import {EmptyTerrain} from "../terrain/EmptyTerrain";
import {Extent} from "../Extent";
import {Entity} from "../entity/Entity";
import {Ellipsoid} from "../ellipsoid/Ellipsoid";
import {EntityCollection} from "../entity/EntityCollection";
import {Geoid} from "../terrain/Geoid";
import type {GeoidModel} from "../terrain/Geoid";
import {GeoImageCreator} from "../utils/GeoImageCreator";
import type {IBaseInputState} from "../renderer/RendererEvents";
import {Key, Lock} from "../Lock";
import {Layer} from "../layer/Layer";
import {Loader} from "../utils/Loader";
import {LonLat} from "../LonLat";
import {Node} from "../quadTree/Node";
import {NormalMapCreator} from "../utils/NormalMapCreator";
import {PlainSegmentWorker} from "../utils/PlainSegmentWorker";
import {IPlanetFlyCartesianParams, PlanetCamera} from "../camera/PlanetCamera";
import {Quat} from "../math/Quat";
import {QuadTreeStrategy} from "../quadTree/QuadTreeStrategy";
import {Ray} from "../math/Ray";
import {RenderNode} from "./RenderNode";
import {SimpleSkyBackground} from "../control/SimpleSkyBackground";
import {Sun} from "../control/Sun";
import {TerrainWorker} from "../utils/TerrainWorker";
import {Vec2, Vec3} from "../math/index";
import type {NumberArray2, NumberArray3, NumberArray4} from "../math/index";
import {Vector} from "../layer/Vector";
import {VectorTileCreator} from "../utils/VectorTileCreator";
import {wgs84} from "../ellipsoid/wgs84";
import type {WebGLBufferExt, WebGLTextureExt, IDefaultTextureParams} from "../webgl/Handler";
import {Program} from "../webgl/Program";
import {Segment} from "../segment/Segment";
import type {AtmosphereParameters} from "../shaders/atmos/atmos";
import { DEFAULT_EASING, DEFAULT_FLIGHT_DURATION } from "../camera/Camera";
import {Easing, EasingFunction} from "../utils/easing";

export interface IPlanetParams {
    name?: string;
    ellipsoid?: Ellipsoid;
    minAltitude?: number;
    maxAltitude?: number;
    frustums?: NumberArray2[];
    maxEqualZoomAltitude?: number;
    minEqualZoomAltitude?: number;
    minEqualZoomCameraSlope?: number;
    quadTreeStrategyPrototype?: typeof QuadTreeStrategy;
    ambient?: string | NumberArray3 | Vec3;
    diffuse?: string | NumberArray3 | Vec3;
    specular?: string | NumberArray3 | Vec3;
    shininess?: number;
    nightTextureSrc?: string | null;
    specularTextureSrc?: string | null;
    maxGridSize?: number;
    maxLoadingRequests?: number;
    atmosphereEnabled?: boolean;
    transitionOpacityEnabled?: boolean;
    atmosphereParameters?: IAtmosphereParams;
    minDistanceBeforeMemClear?: number;
    vectorTileSize?: number;
    maxNodesCount?: number;
}

export type PlanetEventsList = [
    "draw",
    "layeradd",
    "baselayerchange",
    "layerremove",
    "layervisibilitychange",
    "rendercompleted",
    "terraincompleted",
    "layerloadend"
];

/**
 * Maximum created nodes count. The more nodes count the more memory usage.
 * When the maximum node count is exceeded, memClear() will be called.
 * @const
 * @type {number}
 * @default
 */
const DEFAULT_MAX_NODES = 200;

type IndexBufferCacheData = { buffer: WebGLBufferExt | null };

/**
 * Main class for rendering planet
 * @class
 * @extends {RenderNode}
 * @param {string} [options.name="Earth"] - Planet name(Earth by default)
 * @param {Ellipsoid} [options.ellipsoid] - Planet ellipsoid(WGS84 by default)
 * @param {Number} [options.maxGridSize=128] - Segment maximal grid size
 * @param {Number} [options.maxEqualZoomAltitude=15000000.0] - Maximal altitude since segments on the screen become the same zoom level
 * @param {Number} [options.minEqualZoomAltitude=10000.0] - Minimal altitude since segments on the screen become the same zoom level
 * @param {Number} [options.minEqualZoomCameraSlope=0.8] - Minimal camera slope above te globe where segments on the screen become the same zoom level
 *
 * @fires EventsHandler<PlanetEventList>#draw
 * @fires EventsHandler<PlanetEventList>#layeradd
 * @fires EventsHandler<PlanetEventList>#baselayerchange
 * @fires EventsHandler<PlanetEventList>#layerremove
 * @fires EventsHandler<PlanetEventList>#layervisibilitychange
 * @fires EventsHandler<PlanetEventList>#geoimageadd
 */
export class Planet extends RenderNode {

    public events: EventsHandler<PlanetEventsList>;

    public _createdNodesCount: number;

    /**
     * @public
     * @type {Ellipsoid}
     */
    public ellipsoid: Ellipsoid;

    /**
     * @public
     * @override
     * @type {Boolean}
     */
    public override lightEnabled: boolean;

    /**
     * Squared ellipsoid radius.
     * @public
     * @type {number}
     */
    public _planetRadius2: number;

    /**
     * Layers array.
     * @public
     * @type {Array.<Layer>}
     */
    public _layers: Layer[];

    /**
     * Flag to trigger layer update in a next frame
     * @type {boolean}
     * @protected
     */
    protected _updateLayers: boolean;

    /**
     * Current visible imagery tile layers array.
     * @public
     * @type {Array.<Layer>}
     */
    public visibleTileLayers: Layer[];

    /**
     * Current visible vector layers array.
     * @protected
     * @type {Array.<Layer>}
     */
    protected visibleVectorLayers: Vector[];
    protected _visibleVectorLayersByDepthOrder: Vector[][];

    protected _visibleTileLayerSlices: Layer[][];

    /**
     * Vector layers visible nodes with collections.
     * @protected
     * @type {EntityCollection[][]}
     */
    protected _visibleEntityCollections: EntityCollection[][];

    /**
     * There is only one base layer on the globe when layer.isBaseLayer is true.
     * @public
     * @type {Layer}
     */
    public baseLayer: Layer | null;

    /**
     * Terrain provider.
     * @public
     * @type {EmptyTerrain}
     */
    public terrain: EmptyTerrain | null;

    /**
     * Camera is this.renderer.activeCamera pointer.
     * @public
     * @type {PlanetCamera}
     */
    public camera: PlanetCamera;

    /**
     * Screen mouse pointer projected to planet cartesian position.
     * @public
     * @type {Vec3}
     */
    public mousePositionOnEarth: Vec3;

    public emptyTexture: WebGLTextureExt | null;
    public transparentTexture: WebGLTextureExt | null;
    public defaultTexture: WebGLTextureExt | null;

    protected _initialViewExtent: Extent | null;

    /**
     * Layers activity lock.
     * @public
     * @type {Lock}
     */
    public layerLock: Lock;

    /**
     * Terrain providers activity lock.
     * @public
     * @type {Lock}
     */
    public terrainLock: Lock;

    /**
     * Height scale factor. 1 - is normal elevation scale.
     * @public
     * @type {number}
     */
    public _heightFactor: number;

    /**
     * Precomputed indexes array for different grid size segments.
     * @protected
     * @type {Array.<Array.<number>>}
     */
    public _indexesCache: IndexBufferCacheData[][][][][];

    protected _indexesCacheToRemove: IndexBufferCacheData[];
    public _indexesCacheToRemoveCounter: number;

    /**
     * Precomputed texture coordinates buffers for different grid size segments.
     * @public
     * @type {Array.<Array.<number>>}
     */
    public _textureCoordsBufferCache: WebGLBufferExt[];

    public quadTreeStrategyPrototype: typeof QuadTreeStrategy;
    public quadTreeStrategy: QuadTreeStrategy;

    /**
     * Night glowing gl texture.
     * @protected
     */
    protected _nightTexture: WebGLTextureExt | null;

    /**
     * Specular mask gl texture.
     * @protected
     */
    protected _specularTexture: WebGLTextureExt | null;


    public _ambient: Float32Array;
    public _diffuse: Float32Array;
    public _specular: Float32Array;

    protected _maxGridSize: number;

    /**
     * Segment multiple textures size.(4 - convenient value for the most devices)
     * @const
     * @public
     */
    public SLICE_SIZE: number;
    public SLICE_SIZE_4: number;
    public SLICE_SIZE_3: number;

    public _pickingColorArr: Float32Array;
    public _samplerArr: Int32Array;
    public _pickingMaskArr: Int32Array;

    /**
     * GeoImage creator.
     * @public
     * @type{GeoImageCreator}
     */
    public _geoImageCreator: GeoImageCreator;

    /**
     * VectorTileCreator creator.
     * @public
     * @type{VectorTileCreator}
     */
    public _vectorTileCreator: VectorTileCreator;

    /**
     * NormalMapCreator creator.
     * @public
     * @type{NormalMapCreator}
     */
    public _normalMapCreator: NormalMapCreator;

    public _terrainWorker: TerrainWorker;

    public _plainSegmentWorker: PlainSegmentWorker;

    public _tileLoader: Loader<Layer>;

    protected _memKey: Key;

    public _distBeforeMemClear: number;

    protected _prevCamEye: Vec3;

    protected _initialized: boolean;

    protected _collectRenderNodesIsActive: boolean;

    /**
     * Night texture brightness coefficient
     * @type {number}
     */
    public nightTextureCoefficient: number;

    protected _renderScreenNodesPASS: () => void;
    protected _renderScreenNodesWithHeightPASS: () => void;

    protected _atmosphereEnabled: boolean;
    protected _atmosphereMaxMinOpacity: Float32Array;

    public solidTextureOne: WebGLTextureExt | null;
    public solidTextureTwo: WebGLTextureExt | null;

    protected _nightTextureSrc: string | null;
    protected _specularTextureSrc: string | null;

    public transitionTime: number;

    protected _atmosphere: Atmosphere;
    private _minDistanceBeforeMemClear: number = 0;
    private _maxNodes: number;

    constructor(options: IPlanetParams = {}) {
        super(options.name);

        this._createdNodesCount = 0;

        this._atmosphere = new Atmosphere(options.atmosphereParameters);

        this.transitionTime = 580;

        this.ellipsoid = options.ellipsoid || wgs84;

        this.lightEnabled = true;

        this._planetRadius2 = (this.ellipsoid.getPolarSize() - 10000.0) * (this.ellipsoid.getPolarSize() - 10000.0);

        this._layers = [];

        this._updateLayers = false;

        this.visibleTileLayers = [];

        this.visibleVectorLayers = [];
        this._visibleVectorLayersByDepthOrder = [];

        this._visibleTileLayerSlices = [];

        this._visibleEntityCollections = [[]];

        this.baseLayer = null;

        this.terrain = null;

        this.camera = new PlanetCamera(this, {
            frustums: options.frustums,
            eye: new Vec3(25000000, 0, 0),
            look: Vec3.ZERO,
            up: Vec3.NORTH,
            minAltitude: options.minAltitude,
            maxAltitude: options.maxAltitude
        });

        this.mousePositionOnEarth = new Vec3();

        this.emptyTexture = null;
        this.transparentTexture = null;
        this.defaultTexture = null;

        this._initialViewExtent = null;

        this.layerLock = new Lock();

        this.terrainLock = new Lock();

        this._heightFactor = 1.0;

        this._indexesCache = [];
        this._indexesCacheToRemove = [];
        this._indexesCacheToRemoveCounter = 0;

        this._textureCoordsBufferCache = [];

        const quadTreeParams = {
            planet: this,
            maxEqualZoomAltitude: options.maxEqualZoomAltitude,
            minEqualZoomAltitude: options.minEqualZoomAltitude,
            minEqualZoomCameraSlope: options.minEqualZoomCameraSlope,
            transitionOpacityEnabled: options.transitionOpacityEnabled,
        };

        // Used in CameraDepthHandler
        this.quadTreeStrategyPrototype = options.quadTreeStrategyPrototype || EarthQuadTreeStrategy;
        this.quadTreeStrategy = new this.quadTreeStrategyPrototype(quadTreeParams);

        this._nightTexture = null;

        this._specularTexture = null;

        let a = utils.createColorRGB(options.ambient, new Vec3(0.2, 0.2, 0.3));
        let d = utils.createColorRGB(options.diffuse, new Vec3(1.0, 1.0, 1.0));
        let s = utils.createColorRGB(options.specular, new Vec3(0.00063, 0.00055, 0.00032));
        let shininess = options.shininess || 18.0;

        this._ambient = new Float32Array([a.x, a.y, a.z]);
        this._diffuse = new Float32Array([d.x, d.y, d.z]);
        this._specular = new Float32Array([s.x, s.y, s.z, shininess]);

        this._maxGridSize = Math.log2(options.maxGridSize || 256);

        this.SLICE_SIZE = 4;
        this.SLICE_SIZE_4 = this.SLICE_SIZE * 4;
        this.SLICE_SIZE_3 = this.SLICE_SIZE * 3;

        this._maxNodes = options.maxNodesCount || DEFAULT_MAX_NODES;

        this._pickingColorArr = new Float32Array(this.SLICE_SIZE_4);
        this._samplerArr = new Int32Array(this.SLICE_SIZE);
        this._pickingMaskArr = new Int32Array(this.SLICE_SIZE);

        this._geoImageCreator = new GeoImageCreator(this);

        this._vectorTileCreator = new VectorTileCreator(this, options.vectorTileSize, options.vectorTileSize);

        this._normalMapCreator = new NormalMapCreator(this);

        this._terrainWorker = new TerrainWorker(3);

        this._plainSegmentWorker = new PlainSegmentWorker(3);

        this._tileLoader = new Loader(options.maxLoadingRequests || 12);

        this._memKey = new Key();

        this.events = createEvents<PlanetEventsList>(PLANET_EVENTS);

        this._distBeforeMemClear = 0.0;

        this._prevCamEye = new Vec3();

        this._initialized = false;

        this._collectRenderNodesIsActive = true;

        this.nightTextureCoefficient = 2.0;

        this._renderScreenNodesPASS = this._renderScreenNodesPASSNoAtmos;
        this._renderScreenNodesWithHeightPASS = this._renderScreenNodesWithHeightPASSNoAtmos;

        this._atmosphereEnabled = options.atmosphereEnabled || false;
        this._atmosphereMaxMinOpacity = new Float32Array([0.95, 0.28]);

        this.solidTextureOne = null;
        this.solidTextureTwo = null;

        this._nightTextureSrc = options.nightTextureSrc || null;
        this._specularTextureSrc = options.specularTextureSrc || null;
    }

    /**
     * Returns true if current terrain data set is loaded
     */
    public get terrainReady(): boolean {
        return this.quadTreeStrategy.terrainReady;
    }

    public get maxGridSize(): number {
        return this._maxGridSize;
    }

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

    public override getFrameRotation(cartesian: Vec3): Quat {
        return this.ellipsoid.getNorthFrameRotation(cartesian);
    }


    public set atmosphereMaxOpacity(opacity: number) {
        this._atmosphereMaxMinOpacity[0] = opacity;
    }

    public get atmosphereMaxOpacity(): number {
        return this._atmosphereMaxMinOpacity[0];
    }

    public set atmosphereMinOpacity(opacity: number) {
        this._atmosphereMaxMinOpacity[1] = opacity;
    }

    public get atmosphereMinOpacity(): number {
        return this._atmosphereMaxMinOpacity[1];
    }

    public set atmosphereEnabled(enabled: boolean) {
        if (enabled != this._atmosphereEnabled) {
            this._atmosphereEnabled = enabled;
            this._initializeAtmosphere();
        }
    }

    public get atmosphereEnabled(): boolean {
        return this._atmosphereEnabled;
    }

    public set diffuse(rgb: string | NumberArray3 | Vec3) {
        let vec = createColorRGB(rgb);
        this._diffuse = new Float32Array(vec.toArray());
    }

    public set ambient(rgb: string | NumberArray3 | Vec3) {
        let vec = createColorRGB(rgb);
        this._ambient = new Float32Array(vec.toArray());
    }

    public set specular(rgb: string | NumberArray3 | Vec3) {
        let vec = createColorRGB(rgb);
        this._specular = new Float32Array([vec.x, vec.y, vec.y, this._specular[3]]);
    }

    public set shininess(v: number) {
        this._specular[3] = v;
    }

    public get normalMapCreator(): NormalMapCreator {
        return this._normalMapCreator;
    }

    public get layers(): Layer[] {
        return [...this._layers];
    }

    public get sun(): Sun | undefined {
        if (this.renderer && this.renderer.controls.sun)
            return this.renderer.controls.sun as Sun;
    }

    public get sunPos(): Vec3 {
        return this.sun!.sunlight.getPosition();
    }

    /**
     * Add the given control to the renderer of the planet scene.
     * @param {Control} control - Control.
     */
    public addControl(control: Control) {
        control.planet = this;
        control.addTo(this.renderer!);
    }

    /**
     * Add the given controls array to the renderer of the planet.
     * @param {Array.<Control>} cArr - Control array.
     */
    public addControls(cArr: Control[]) {
        for (let i = 0; i < cArr.length; i++) {
            this.addControl(cArr[i]);
        }
    }

    /**
     * Return layer by it name
     * @param {string} name - Name of the layer. og.Layer.prototype.name
     * @public
     * @returns {Layer} -
     */
    public getLayerByName(name: string): Layer | undefined {
        for (let i = 0, len = this._layers.length; i < len; i++) {
            if (name === this._layers[i].name) {
                return this._layers[i];
            }
        }
    }

    /**
     * Adds layer to the planet.
     * @param {Layer} layer - Layer object.
     * @public
     */
    public addLayer(layer: Layer) {
        layer.addTo(this);
    }

    /**
     * Dispatch layer visibility changing event.
     * @param {Layer} layer - Changed layer.
     * @public
     */
    public _onLayerVisibilityChanged(layer: Layer) {
        this.events.dispatch(this.events.layervisibilitychange, layer);
    }

    /**
     * Adds the given layers array to the planet.
     * @param {Array.<Layer>} layers - Layers array.
     * @public
     */
    public addLayers(layers: Layer[]) {
        for (let i = 0, len = layers.length; i < len; i++) {
            this.addLayer(layers[i]);
        }
    }

    /**
     * Removes the given layer from the planet.
     * @param {Layer} layer - Layer to remove.
     * @public
     */
    public removeLayer(layer: Layer) {
        layer.remove();
    }

    /**
     *
     * @public
     * @param {Layer} layer - Material layer.
     */
    public _clearLayerMaterial(layer: Layer) {
        this.quadTreeStrategy.clearLayerMaterial(layer);
    }

    /**
     * Sets base layer coverage to the planet.
     * @param {Layer} layer - Layer object.
     * @public
     */
    public setBaseLayer(layer: Layer) {
        if (this.baseLayer) {
            if (!this.baseLayer.isEqual(layer)) {
                this.baseLayer.setVisibility(false);
                this.baseLayer = layer;
                layer.setVisibility(true);
                this.events.dispatch(this.events.baselayerchange, layer);
            }
        } else {
            this.baseLayer = layer;
            this.baseLayer.setVisibility(true);
            this.events.dispatch(this.events.baselayerchange, layer);
        }
    }

    /**
     * Sets elevation scale. 1.0 is default.
     * @param {number} factor - Elevation scale.
     */
    public setHeightFactor(factor: number) {
        if (this._heightFactor !== factor) {
            this._heightFactor = factor;
            this.quadTreeStrategy.destroyBranches();
            this.quadTreeStrategy.clearRenderedNodes();
        }
    }

    /**
     * Gets elevation scale.
     * @returns {number} Terrain elevation scale
     */
    public getHeightFactor(): number {
        return this._heightFactor;
    }

    /**
     * Sets terrain provider
     * @public
     * @param {EmptyTerrain} terrain - Terrain provider.
     */
    public setTerrain(terrain: EmptyTerrain) {
        if (this._initialized) {
            this.memClear();
        }

        if (this.terrain) {
            this.terrain.abortLoading();
            this.terrain.clearCache();
            this.terrain._planet = null;
        }

        this.terrain = terrain;
        this.terrain._planet = this;

        this.quadTreeStrategy.destroyBranches();

        if (terrain._geoid.model) {
            this._plainSegmentWorker.setGeoid(terrain.getGeoid());
            terrain._isReady = true;
        } else {
            Geoid.loadModel(terrain.geoid.src)
                .then((m: GeoidModel | null) => {
                    terrain.geoid.setModel(m);
                    this._plainSegmentWorker.setGeoid(terrain.getGeoid());
                    terrain._isReady = true;
                })
                .catch((err) => {
                    console.warn(err);
                });
        }
    }

    public initAtmosphereShader(atmosParams?: AtmosphereParameters) {
        if (this.renderer && this.renderer.handler && this._atmosphereEnabled) {
            let h = this.renderer.handler;
            if (h.isWebGl2()) {
                h.removeProgram("drawnode_screen_wl");
                h.addProgram(shaders.drawnode_screen_wl_webgl2Atmos(atmosParams));
            } else {
                console.warn("Atmosphere WebGL2 only");
            }
        }
    }

    public get atmosphereControl(): Atmosphere {
        return this._atmosphere;
    }

    protected _initializeAtmosphere() {

        if (!this.renderer) return;

        let h = this.renderer.handler;

        h.removeProgram("drawnode_screen_wl");

        if (this._atmosphereEnabled) {

            this._renderScreenNodesPASS = this._renderScreenNodesPASSAtmos;
            this._renderScreenNodesWithHeightPASS = this._renderScreenNodesWithHeightPASSAtmos;

            if (!this.renderer.controls.Atmosphere) {
                this.addControl(this._atmosphere);
            }

            this._atmosphere.activate();

            if (h.isWebGl2()) {
                h.addProgram(shaders.drawnode_screen_wl_webgl2Atmos(this._atmosphere.parameters));
            } else {
                h.addProgram(shaders.drawnode_screen_wl_webgl1NoAtmos());
            }

            if (this.renderer.controls.SimpleSkyBackground) {
                this.renderer.controls.SimpleSkyBackground.deactivate();
            }

        } else {

            this._renderScreenNodesPASS = this._renderScreenNodesPASSNoAtmos;
            this._renderScreenNodesWithHeightPASS = this._renderScreenNodesWithHeightPASSNoAtmos;

            this._atmosphere.deactivate();

            if (!this.renderer.controls.SimpleSkyBackground) {
                this.addControl(new SimpleSkyBackground());
            } else {
                this.renderer.controls.SimpleSkyBackground.activate();
            }

            if (h.isWebGl2()) {
                h.addProgram(shaders.drawnode_screen_wl_webgl2NoAtmos());
            } else {
                h.addProgram(shaders.drawnode_screen_wl_webgl1NoAtmos());
            }
        }
    }

    protected _initializeShaders() {
        if (!this.renderer) {
            throw new Error("Renderer is not initialized");
        }

        let r = this.renderer,
            h = r.handler;

        h.addProgram(shaders.drawnode_screen_nl());
        h.addProgram(shaders.drawnode_colorPicking());
        h.addProgram(shaders.drawnode_depth());

        r.addPickingCallback(this, this._renderColorPickingFramebufferPASS);
        r.addDepthCallback(this, () => {
            this.renderDepthFramebuffer(this.camera, this.quadTreeStrategy);
        });
    }

    protected _onLayerLoadend(layer: Layer) {
        this.events.dispatch(this.events.layerloadend, layer);
    }

    public override init() {

        this._tileLoader.events.on("layerloadend", this._onLayerLoadend, this);

        // Initialization indexes table
        segmentHelper.getInstance().setMaxGridSize(this._maxGridSize);

        const TABLESIZE = this._maxGridSize;

        let kk = 0;
        // Initialization indexes buffers cache. It takes about 120mb RAM!
        for (let i = 0; i <= TABLESIZE; i++) {
            !this._indexesCache[i] && (this._indexesCache[i] = new Array(TABLESIZE));
            for (let j = 0; j <= TABLESIZE; j++) {
                !this._indexesCache[i][j] && (this._indexesCache[i][j] = new Array(TABLESIZE));
                for (let k = 0; k <= TABLESIZE; k++) {
                    !this._indexesCache[i][j][k] && (this._indexesCache[i][j][k] = new Array(TABLESIZE));
                    for (let m = 0; m <= TABLESIZE; m++) {
                        !this._indexesCache[i][j][k][m] && (this._indexesCache[i][j][k][m] = new Array(TABLESIZE));
                        for (let q = 0; q <= TABLESIZE; q++) {

                            let ptr: IndexBufferCacheData = {
                                buffer: null
                            };

                            if (i >= 1 && i === j && i === k && i === m && i === q) {
                                let indexes = segmentHelper.getInstance().createSegmentIndexes(i, [j, k, m, q]);
                                ptr.buffer = this.renderer!.handler.createElementArrayBuffer(indexes, 1);
                            } else {
                                this._indexesCacheToRemove[kk++] = ptr;
                            }

                            this._indexesCache[i][j][k][m][q] = ptr;
                        }
                    }
                }
            }
        }

        this.renderer!.events.on("drawtransparent", () => {
            this._renderScreenNodesWithHeightPASS();
        });

        // Initialize texture coordinates buffer pool
        this._textureCoordsBufferCache = [];

        let texCoordCache = segmentHelper.getInstance().initTextureCoordsTable(TABLESIZE + 1);

        for (let i = 0; i <= TABLESIZE; i++) {
            this._textureCoordsBufferCache[i] = this.renderer!.handler.createArrayBuffer(texCoordCache[i], 2, ((1 << i) + 1) * ((1 << i) + 1));
        }

        // creating empty textures
        this.renderer!.handler.createDefaultTexture(null, (t: WebGLTextureExt) => {
            this.solidTextureOne = t;
            this.solidTextureTwo = t;
        });

        this.transparentTexture = this.renderer!.handler.transparentTexture;

        this.drawMode = this.renderer!.handler.gl!.TRIANGLE_STRIP;

        // Applying shaders
        this._initializeShaders();

        this._initializeAtmosphere();

        this._updateVisibleLayers();

        // loading Earth night glowing texture
        if (this._nightTextureSrc) {

            let img = new Image();
            img.crossOrigin = "Anonymous";
            img.onload = () => {
                this._nightTexture = this.renderer!.handler.createTextureDefault(img)!;
                this._nightTexture.default = true;
            };
            img.src = this._nightTextureSrc;

            // createImageBitmap(NIGHT).then((e: ImageBitmap) => {
            //     this._nightTexture = this.renderer!.handler!.createTextureDefault(e);
            // });
        }

        // load water specular mask
        if (this._specularTextureSrc) {
            let img = new Image();
            img.crossOrigin = "Anonymous";
            img.onload = () => {
                this._specularTexture = this.renderer!.handler.createTextureDefault(img)!;
                this._specularTexture.default = true;
            };
            img.src = this._specularTextureSrc;

            // createImageBitmap(SPECULAR).then((e: ImageBitmap) => {
            //     this._specularTexture = this.renderer!.handler!.createTexture_l(e);
            // });
        }

        this._geoImageCreator.init();

        this._vectorTileCreator.init();

        this._normalMapCreator.init();

        this.renderer!.events.on("draw", this._globalPreDraw, this, -100);

        // Creating quad trees nodes
        this.quadTreeStrategy.init(this.camera);

        // this.renderer!.events.on("postdraw", () => {
        //     this._checkRendercompleted();
        // });

        this.initLayers();

        this._initialized = true;

        //
        // after init
        //
        if (this._initialViewExtent) {
            this.viewExtent(this._initialViewExtent);
        }

        this.renderer!.activeCamera = this.camera;
        //this.camera.bindRenderer(this.renderer!);
        this.camera.bindFrustumsPickingColors(this.renderer!);
        this.camera.update();
    }

    public initLayers() {
        let temp = [...this._layers];
        for (let i = 0; i < temp.length; i++) {
            this.removeLayer(temp[i]);
            this.addLayer(temp[i]);
        }
    }

    protected _clearIndexesCache() {
        this._indexesCacheToRemoveCounter = 0;
        let c = this._indexesCacheToRemove,
            gl = this.renderer!.handler.gl!;
        for (let i = 0, len = c.length; i < len; i++) {
            let ci = c[i];
            gl.deleteBuffer(ci.buffer as WebGLBuffer);
            ci.buffer = null;
        }
    }

    /**
     * Creates default textures first for the North Pole and whole globe and second for the South Pole.
     * @public
     * @param{IDefaultTextureParams} param0 -
     * @param{IDefaultTextureParams} param1 -
     */
    public createDefaultTextures(param0: IDefaultTextureParams, param1: IDefaultTextureParams) {
        this.renderer!.handler.gl!.deleteTexture(this.solidTextureOne!);
        this.renderer!.handler.gl!.deleteTexture(this.solidTextureTwo!);
        this.renderer!.handler.createDefaultTexture(param0, (texture: WebGLTextureExt) => {
            this.solidTextureOne = texture;
        });
        this.renderer!.handler.createDefaultTexture(param1, (texture: WebGLTextureExt) => {
            this.solidTextureTwo = texture;
        });
    }

    protected _getLayerAttributionHTML(layer: Layer) {
        return `<div class="og-attribution__layer">${layer.getAttribution()}</div>`;
    }

    /**
     * Updates attribution lists
     * @public
     */
    public updateAttributionsList() {
        let html = "";
        for (let i = 0, len = this._layers.length; i < len; i++) {
            let li = this._layers[i];
            if (li.getVisibility()) {
                if (li.getAttribution().length) {
                    html += this._getLayerAttributionHTML(li);
                }
            }
        }
        this._applyAttribution(html)
    }

    public updateVisibleLayers() {
        this._updateLayers = true;
    }

    protected _updateVisibleLayers() {
        this.visibleTileLayers = [];
        this.visibleTileLayers.length = 0;

        this.visibleVectorLayers = [];
        this.visibleVectorLayers.length = 0;

        let html = "";
        for (let i = 0, len = this._layers.length; i < len; i++) {
            let li = this._layers[i];
            if (li.getVisibility()) {
                if (li.isBaseLayer()) {
                    this.createDefaultTextures(li._defaultTextures[0]!, li._defaultTextures[1]!);
                    this.baseLayer = li;
                }

                if (li.hasImageryTiles()) {
                    this.visibleTileLayers.push(li);
                }

                if (li.isVector) {
                    this.visibleVectorLayers.push(li as Vector);
                }

                if (li.getAttribution().length) {
                    html += this._getLayerAttributionHTML(li);
                }

            } else if (li._fading && li._fadingOpacity > 0) {
                if (li.hasImageryTiles()) {
                    this.visibleTileLayers.push(li);
                }

                if (li.isVector) {
                    this.visibleVectorLayers.push(li as Vector);
                }
            }
        }

        this._applyAttribution(html);

        this._sortLayers();
    }

    /**
     * Apply to render list of layer attributions
     * @protected
     */
    protected _applyAttribution(html: string) {
        if (this.renderer && this.renderer.div) {
            if (html.length) {
                if (this.renderer.div.attributions!.innerHTML !== html) {
                    this.renderer.div.attributions!.innerHTML = html;
                }
            } else {
                this.renderer.div.attributions!.innerHTML = "";
            }
        }
    }

    /**
     * Sort visible layer - preparing for rendering.
     * @protected
     */
    protected _sortLayers() {

        this.visibleVectorLayers.sort((a, b) => (a.getZIndex() - b.getZIndex()) || (a.getHeight() - b.getHeight()));

        let grouped: Record<number, Vector[]> = {0: []};
        for (const vi of this.visibleVectorLayers) {
            if (!grouped[vi.depthOrder]) {
                grouped[vi.depthOrder] = [];
            }
            grouped[vi.depthOrder].push(vi);
        }

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

        this._visibleTileLayerSlices = [];
        this._visibleTileLayerSlices.length = 0;

        if (this.visibleTileLayers.length) {
            this.visibleTileLayers.sort((a, b) => (a.getHeight() - b.getHeight()) || (a.getZIndex() - b.getZIndex()));

            let k = -1;
            let currHeight = this.visibleTileLayers[0].getHeight();
            for (let i = 0, len = this.visibleTileLayers.length; i < len; i++) {
                if (i % this.SLICE_SIZE === 0 || this.visibleTileLayers[i].getHeight() !== currHeight) {
                    k++;
                    this._visibleTileLayerSlices[k] = [];
                    currHeight = this.visibleTileLayers[i].getHeight();
                }
                this._visibleTileLayerSlices[k].push(this.visibleTileLayers[i]);
            }
        }
    }

    protected _renderScreenNodesPASSNoAtmos() {
        let cam = this.camera;
        let sh = this._setUniformsNoAtmos(cam);
        //
        // PASS 0: rendering base slice of layers, which is often zero height
        this._renderingScreenNodes(this.quadTreeStrategy, sh, cam, this.quadTreeStrategy._renderedNodesInFrustum[cam.currentFrustumIndex]);
    }

    protected _renderScreenNodesPASSAtmos() {
        let cam = this.camera;
        let sh = this._setUniformsAtmos(cam);
        //
        // PASS 0: rendering base slice of layers, which is often zero height
        this._renderingScreenNodes(this.quadTreeStrategy, sh, cam, this.quadTreeStrategy._renderedNodesInFrustum[cam.currentFrustumIndex]);
    }

    protected _renderScreenNodesWithHeightPASSNoAtmos() {
        let cam = this.camera;
        let sh = this._setUniformsNoAtmos(cam);
        //
        // PASS 1: rendering slices, and layers with heights, without transition opacity effect
        this._renderingScreenNodesWithHeight(this.quadTreeStrategy, sh, cam, this.quadTreeStrategy._renderedNodesInFrustum[cam.currentFrustumIndex]);
    }

    protected _renderScreenNodesWithHeightPASSAtmos() {
        let cam = this.camera;
        let sh = this._setUniformsAtmos(cam);
        //
        // PASS 1: rendering slices, and layers with heights, without transition opacity effect
        this._renderingScreenNodesWithHeight(this.quadTreeStrategy, sh, cam, this.quadTreeStrategy._renderedNodesInFrustum[cam.currentFrustumIndex]);
    }

    protected _globalPreDraw() {
        let cam = this.camera;

        this._distBeforeMemClear += this._prevCamEye.distance(cam.eye);
        this._prevCamEye.copy(cam.eye);

        // free memory
        if (this._createdNodesCount > this._maxNodes && this._distBeforeMemClear > this._minDistanceBeforeMemClear) {
            this.terrain!.clearCache();
            this.memClear();
        }

        if (this._indexesCacheToRemoveCounter > 600) {
            this._clearIndexesCache();
        }
    }

    /**
     * Render node callback.
     * @public
     */
    public override preFrame() {

        if (this._updateLayers) {
            this._updateLayers = false;
            this._updateVisibleLayers();
        }

        if (this.camera.isFirstPass) {
            this.camera.update();

            if (this._collectRenderNodesIsActive) {
                this.quadTreeStrategy.collectRenderNodes(this.camera);
            }

            //this.transformLights();

            // creates terrain normal maps
            this._normalMapCreator.frame();

            // Creating geoImages textures.
            this._geoImageCreator.frame();

            // Vector tiles rasterization
            this._vectorTileCreator.frame();

            this.camera.checkTerrainCollision();
            this.camera.update();

            // Here is the planet node dispatches a draw event before
            // rendering begins, and we have got render nodes.
            this.events.dispatch(this.events.draw, this);

            // Collect entity collections from vector layers
            this._collectVectorLayerCollections();
        }

        for (let i = 0; i < this._visibleEntityCollections.length; i++) {
            this.drawEntityCollections(this._visibleEntityCollections[i], i);
        }
    }

    /**
     * Render node callback.
     * Frame function is called for each renderer activrCamera frustum.
     * @public
     * @override
     */
    public override frame() {
        this._renderScreenNodesPASS();
    }

    public lockQuadTree() {
        this._collectRenderNodesIsActive = false;
        this.camera.setTerrainCollisionActivity(false);
    }

    public unlockQuadTree() {
        this._collectRenderNodesIsActive = true;
        this.camera.setTerrainCollisionActivity(true);
    }

    protected _setUniformsNoAtmos(cam: PlanetCamera): Program {
        let sh, shu;
        let renderer = this.renderer!;

        let h = renderer.handler;
        let gl = h.gl!;

        gl.enable(gl.CULL_FACE);

        renderer.enableBlendOneSrcAlpha();

        if (this.lightEnabled) {
            h.programs.drawnode_screen_wl.activate();
            sh = h.programs.drawnode_screen_wl._program;
            shu = sh.uniforms;

            gl.uniform3fv(shu.lightPosition, this._lightPosition);
            gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
            gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());

            if (this.baseLayer) {
                gl.uniform3fv(shu.diffuse, this.baseLayer._diffuse || this._diffuse);
                gl.uniform3fv(shu.ambient, this.baseLayer._ambient || this._ambient);
                gl.uniform4fv(shu.specular, this.baseLayer._specular || this._specular);
                gl.uniform1f(shu.nightTextureCoefficient, this.baseLayer.nightTextureCoefficient || this.nightTextureCoefficient);
            } else {
                gl.uniform3fv(shu.diffuse, this._diffuse);
                gl.uniform3fv(shu.ambient, this._ambient);
                gl.uniform4fv(shu.specular, this._specular);
                gl.uniform1f(shu.nightTextureCoefficient, this.nightTextureCoefficient);
            }

            //
            // Night and specular
            //
            gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE);
            gl.bindTexture(gl.TEXTURE_2D, this._nightTexture! || this.transparentTexture!);
            gl.uniform1i(shu.nightTexture, this.SLICE_SIZE);

            gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE + 1);
            gl.bindTexture(gl.TEXTURE_2D, this._specularTexture! || this.transparentTexture!);
            gl.uniform1i(shu.specularTexture, this.SLICE_SIZE + 1);

            gl.uniform1f(shu.camHeight, cam.getHeight());

        } else {
            h.programs.drawnode_screen_nl.activate();
            sh = h.programs.drawnode_screen_nl._program;
            shu = sh.uniforms;
            gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
            gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());
        }

        gl.uniform3fv(shu.eyePositionHigh, cam.eyeHigh);
        gl.uniform3fv(shu.eyePositionLow, cam.eyeLow);

        return sh;
    }

    protected _setUniformsAtmos(cam: PlanetCamera): Program {

        let sh, shu;
        let renderer = this.renderer!;
        let h = renderer.handler;
        let gl = h.gl!;

        gl.enable(gl.CULL_FACE);

        renderer.enableBlendOneSrcAlpha();

        if (this.lightEnabled) {
            h.programs.drawnode_screen_wl.activate();
            sh = h.programs.drawnode_screen_wl._program;
            shu = sh.uniforms;

            gl.uniform3fv(shu.lightPosition, this._lightPosition);
            gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
            gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());

            if (this.baseLayer) {
                gl.uniform3fv(shu.diffuse, this.baseLayer._diffuse || this._diffuse);
                gl.uniform3fv(shu.ambient, this.baseLayer._ambient || this._ambient);
                gl.uniform4fv(shu.specular, this.baseLayer._specular || this._specular);
                gl.uniform1f(shu.nightTextureCoefficient, this.baseLayer.nightTextureCoefficient || this.nightTextureCoefficient);
            } else {
                gl.uniform3fv(shu.diffuse, this._diffuse);
                gl.uniform3fv(shu.ambient, this._ambient);
                gl.uniform4fv(shu.specular, this._specular);
                gl.uniform1f(shu.nightTextureCoefficient, this.nightTextureCoefficient);
            }

            gl.uniform2fv(shu.maxMinOpacity, this._atmosphereMaxMinOpacity);

            //
            // Night and specular
            //
            gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE);
            gl.bindTexture(gl.TEXTURE_2D, this._nightTexture! || this.transparentTexture!);
            gl.uniform1i(shu.nightTexture, this.SLICE_SIZE);

            gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE + 1);
            gl.bindTexture(gl.TEXTURE_2D, this._specularTexture! || this.transparentTexture!);
            gl.uniform1i(shu.specularTexture, this.SLICE_SIZE + 1);

            //
            // atmos precomputed textures
            //
            gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE + 4);
            gl.bindTexture(gl.TEXTURE_2D, (renderer.controls.Atmosphere as Atmosphere)._transmittanceBuffer!.textures[0]);
            gl.uniform1i(shu.transmittanceTexture, this.SLICE_SIZE + 4);

            gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE + 5);
            gl.bindTexture(gl.TEXTURE_2D, (renderer.controls.Atmosphere as Atmosphere)._scatteringBuffer!.textures[0]);
            gl.uniform1i(shu.scatteringTexture, this.SLICE_SIZE + 5);

            gl.uniform1f(shu.camHeight, cam.getHeight());

        } else {
            h.programs.drawnode_screen_nl.activate();
            sh = h.programs.drawnode_screen_nl._program;
            shu = sh.uniforms;
            gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
            gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());
        }

        gl.uniform3fv(shu.eyePositionHigh, cam.eyeHigh);
        gl.uniform3fv(shu.eyePositionLow, cam.eyeLow);

        return sh;
    }

    protected _renderingFadingNodes = (
        quadTreeStrategy: QuadTreeStrategy,
        nodes: Map<number, boolean>,
        sh: Program,
        currentNode: Node,
        sl: Layer[],
        sliceIndex: number,
        outTransparentSegments?: Segment[],
        outOpaqueSegments?: Segment[]
    ) => {

        let isFirstPass = sliceIndex === 0;
        let isEq = this.terrain!.equalizeVertices;

        for (let j = 0, len = currentNode._fadingNodes.length; j < len; j++) {
            let f = currentNode._fadingNodes[j].segment;
            if (quadTreeStrategy._fadingNodes.has(currentNode._fadingNodes[0].__id) && !nodes.has(f.node.__id)) {
                nodes.set(f.node.__id, true);

                if (f._transitionOpacity < 1.0) {
                    outTransparentSegments!.push(f);
                } else {
                    if (isFirstPass) {
                        isEq && f.equalize();
                        f.readyToEngage && f.engage();
                        f.screenRendering(sh, sl, sliceIndex);
                        outOpaqueSegments!.push(f);
                    } else {
                        f.screenRendering(sh, sl, sliceIndex, this.transparentTexture, true);
                    }
                }
            }
        }
    }

    protected _renderingFadingNodesNoDepth = (
        quadTreeStrategy: QuadTreeStrategy,
        nodes: Map<number, boolean>,
        sh: Program,
        currentNode: Node,
        sl: Layer[],
        sliceIndex: number,
        outOpaqueSegments?: Segment[]
    ) => {

        let isFirstPass = sliceIndex === 0;
        let isEq = this.terrain!.equalizeVertices;
        let gl = sh.gl!;

        gl.disable(gl.DEPTH_TEST);

        for (let j = 0, len = currentNode._fadingNodes.length; j < len; j++) {
            let f = currentNode._fadingNodes[j].segment;
            if (quadTreeStrategy._fadingNodes.has(currentNode._fadingNodes[0].__id) && !nodes.has(f.node.__id)) {
                nodes.set(f.node.__id, true);
                if (isFirstPass) {
                    isEq && f.equalize();
                    f.readyToEngage && f.engage();
                    f.screenRendering(sh, sl, sliceIndex);
                    outOpaqueSegments!.push(f);
                } else {
                    f.screenRendering(sh, sl, sliceIndex, this.transparentTexture, true);
                }
            }
        }

        gl.enable(gl.DEPTH_TEST);
    }

    protected static __refreshLayersFadingOpacity__(layersRef: Layer[], minCurrZoom: number, maxCurrZoom: number) {
        for (let i = layersRef.length - 1; i >= 0; --i) {
            let li = layersRef[i];
            if (li._fading && li._refreshFadingOpacity(minCurrZoom, maxCurrZoom)) {
                layersRef.splice(i, 1);
            }
        }
    }

    /**
     * Drawing nodes
     */
    protected _renderingScreenNodes(
        quadTreeStrategy: QuadTreeStrategy,
        sh: Program,
        cam: PlanetCamera,
        renderedNodes: Node[]
    ) {

        let sl = this._visibleTileLayerSlices;

        if (sl.length && cam.isFirstPass) {
            Planet.__refreshLayersFadingOpacity__(sl[0], quadTreeStrategy.minCurrZoom, quadTreeStrategy.maxCurrZoom);
        }

        let nodes = new Map<number, boolean>;
        let transparentSegments: Segment[] = [];

        let isEq = this.terrain!.equalizeVertices;
        let i = renderedNodes.length;

        //
        // Collect fading opaque segments, because we need them in the framebuffer passes,
        // as the segments with equalized sides, which means that there are no gaps
        // between currently rendered neighbours
        //
        quadTreeStrategy._fadingOpaqueSegments = [];

        if (cam.slope > 0.8 || !this.terrain || this.terrain.isEmpty /*|| cam.getAltitude() > 10000*/) {
            while (i--) {
                let ri = renderedNodes[i];
                let s = ri.segment;

                this._renderingFadingNodesNoDepth(quadTreeStrategy, nodes, sh, ri, sl[0], 0, quadTreeStrategy._fadingOpaqueSegments);

                isEq && s.equalize();
                s.readyToEngage && s.engage();
                s.screenRendering(sh, sl[0], 0);
            }
        } else {

            //
            // Render opaque segments on the first pass, remove transparent ones into second pass
            //
            while (i--) {
                let ri = renderedNodes[i];
                let s = ri.segment;

                this._renderingFadingNodes(quadTreeStrategy, nodes, sh, ri, sl[0], 0, transparentSegments, quadTreeStrategy._fadingOpaqueSegments);

                if (s._transitionOpacity < 1) {
                    transparentSegments.push(s);
                } else {
                    isEq && s.equalize();
                    s.readyToEngage && s.engage();
                    s.screenRendering(sh, sl[0], 0);
                }
            }

            //
            // Render transparent segments
            //
            for (let j = 0; j < transparentSegments.length; j++) {
                let tj = transparentSegments[j];

                isEq && tj.equalize();
                tj.readyToEngage && tj.engage();
                tj.screenRendering(sh, sl[0], 0);
            }
        }
    }

    protected _renderingScreenNodesWithHeight(
        quadTreeStrategy: QuadTreeStrategy,
        sh: Program,
        cam: PlanetCamera,
        renderedNodes: Node[]
    ) {

        let gl = this.renderer!.handler.gl!;

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

        let nodes = new Map<number, boolean>;
        let transparentSegments: Segment[] = [];

        let sl = this._visibleTileLayerSlices;

        for (let j = 1, len = sl.length; j < len; j++) {

            if (cam.isFirstPass) {
                Planet.__refreshLayersFadingOpacity__(sl[j], quadTreeStrategy.minCurrZoom, quadTreeStrategy.maxCurrZoom);
            }

            gl.polygonOffset(0, -j);
            let i = renderedNodes.length;
            while (i--) {
                let ri = renderedNodes[i];
                this._renderingFadingNodes(quadTreeStrategy, nodes, sh, ri, sl[j], j, transparentSegments);
                if (ri.segment._transitionOpacity < 1) {
                    ri.segment.initSlice(j);
                } else {
                    ri.segment.screenRendering(sh, sl[j], j, this.transparentTexture, true);
                }
            }
        }

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

    protected _renderColorPickingFramebufferPASS() {
        let sh;
        let renderer = this.renderer!;
        let h = renderer.handler;
        let gl = h.gl!;
        h.programs.drawnode_colorPicking.activate();
        sh = h.programs.drawnode_colorPicking._program;
        let shu = sh.uniforms;
        let cam = renderer.activeCamera!;

        gl.enable(gl.CULL_FACE);

        gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
        gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());

        gl.uniform3fv(shu.eyePositionHigh, cam.eyeHigh);
        gl.uniform3fv(shu.eyePositionLow, cam.eyeLow);

        // drawing planet nodes
        let rn = this.quadTreeStrategy._renderedNodesInFrustum[cam.getCurrentFrustum()];
        let sl = this._visibleTileLayerSlices;

        let i = rn.length;
        while (i--) {
            if (rn[i].segment._transitionOpacity >= 1) {
                rn[i].segment.colorPickingRendering(sh, sl[0], 0);
            }
        }

        for (let i = 0; i < this.quadTreeStrategy._fadingOpaqueSegments.length; ++i) {
            this.quadTreeStrategy._fadingOpaqueSegments[i].colorPickingRendering(sh, sl[0], 0);
        }

        // Here is set blending for transparent overlays
        renderer.enableBlendDefault();

        gl.enable(gl.POLYGON_OFFSET_FILL);
        for (let j = 1, len = sl.length; j < len; j++) {
            i = rn.length;
            gl.polygonOffset(0, -j);
            while (i--) {
                rn[i].segment.colorPickingRendering(sh, sl[j], j, this.transparentTexture, true);
            }
        }

        gl.disable(gl.POLYGON_OFFSET_FILL);
    }

    public renderDepthFramebuffer(cam: PlanetCamera, quadTreeStrategy: QuadTreeStrategy) {
        let sh;
        let renderer = this.renderer!;
        let h = renderer.handler;
        let gl = h.gl!;
        h.programs.drawnode_depth.activate();
        sh = h.programs.drawnode_depth._program;
        let shu = sh.uniforms;

        gl.disable(gl.BLEND);
        gl.disable(gl.POLYGON_OFFSET_FILL);

        gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
        gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());

        gl.uniform3fv(shu.eyePositionHigh, cam.eyeHigh);
        gl.uniform3fv(shu.eyePositionLow, cam.eyeLow);

        gl.uniform1f(shu.frustumPickingColor, cam.frustumColorIndex);

        // drawing planet nodes
        let rn = quadTreeStrategy._renderedNodesInFrustum[cam.getCurrentFrustum()],
            sl = this._visibleTileLayerSlices;

        let i = rn.length;
        while (i--) {
            if (rn[i].segment._transitionOpacity >= 1) {
                rn[i].segment.depthRendering(sh, sl[0]);
            }
        }

        for (let i = 0; i < quadTreeStrategy._fadingOpaqueSegments.length; ++i) {
            quadTreeStrategy._fadingOpaqueSegments[i].depthRendering(sh, sl[0]);
        }

        gl.enable(gl.BLEND);
    }

    protected _collectVectorLayerCollections() {

        let k = this._visibleVectorLayersByDepthOrder.length;
        this._visibleEntityCollections.length = 0;
        this._visibleEntityCollections = new Array(k);
        for (let i = 0; i < this._visibleEntityCollections.length; i++) {
            this._visibleEntityCollections[i] = [];
        }

        while (k--) {
            let group = this._visibleVectorLayersByDepthOrder[k];
            let j = group.length;
            while (j--) {
                let vi = group[j];
                if (vi._fading && vi._refreshFadingOpacity(this.quadTreeStrategy.minCurrZoom, this.quadTreeStrategy.maxCurrZoom)) {
                    group.splice(j, 1);
                    if (group.length === 0) {
                        this._visibleVectorLayersByDepthOrder.splice(k, 1);
                    }
                }

                vi.collectVisibleCollections(this._visibleEntityCollections[k]);
                vi.update();
            }
        }
    }

    /**
     * Starts clear memory thread.
     * @public
     */
    public memClear() {
        this._distBeforeMemClear = 0;

        this.camera._insideSegment = null;

        this.layerLock.lock(this._memKey);
        this.terrainLock.lock(this._memKey);
        this._normalMapCreator.lock(this._memKey);

        this._normalMapCreator.clear();
        this.terrain!.abortLoading();
        this._tileLoader.abortAll();

        this.quadTreeStrategy.clear();
        this.layerLock.free(this._memKey);
        this.terrainLock.free(this._memKey);
        this._normalMapCreator.free(this._memKey);

        this._createdNodesCount = 0;
    }

    /**
     * Returns ray vector hit ellipsoid coordinates.
     * If the ray doesn't hit ellipsoid it returns 'undefined'.
     * @public
     * @param {Ray} ray - Ray.
     * @returns {Vec3 | undefined} -
     */
    public getRayIntersectionEllipsoid(ray: Ray): Vec3 | undefined {
        return this.ellipsoid.hitRay(ray.origin, ray.direction);
    }

    /**
     * Project screen coordinates to the planet ellipsoid.
     * @public
     * @param {Vec2 | IBaseInputState } px - Screen coordinates.
     * @returns {Vec3 | undefined} - Cartesian coordinates.
     */
    public getCartesianFromPixelEllipsoid(px: Vec2 | IBaseInputState): Vec3 | undefined {
        let cam = this.renderer!.activeCamera!;
        return this.ellipsoid.hitRay(cam.eye, cam.unproject(px.x, px.y));
    }

    /**
     * Project screen coordinates to the planet ellipsoid.
     * @public
     * @param {Vec2 | IBaseInputState} px - Screen coordinates.
     * @returns {LonLat | undefined} - Geodetic coordinates.
     */
    public getLonLatFromPixelEllipsoid(px: Vec2): LonLat | undefined {
        let coords = this.getCartesianFromPixelEllipsoid(px);
        if (coords) {
            return this.ellipsoid.cartesianToLonLat(coords);
        }
    }

    /**
     * Returns mouse position cartesian coordinates on the current terrain.
     * @public
     * @returns {Vec3 | undefined} -
     */
    public getCartesianFromMouseTerrain(): Vec3 | undefined {
        let ms = this.renderer!.events.mouseState;
        let distance = this.getDistanceFromPixel(ms);
        if (distance) {
            return ms.direction.scaleTo(distance).addA(this.renderer!.activeCamera!.eye);
        }
    }

    /**
     * Returns screen coordinates cartesian coordinates on the current terrain.
     * position or null if input coordinates is outside the planet.
     * @public
     * @param {Vec2} px - Pixel screen 2d coordinates.
     * @returns {Vec3 | undefined} -
     */
    public getCartesianFromPixelTerrain(px: Vec2 | IBaseInputState): Vec3 | undefined {
        let distance = this.getDistanceFromPixel(px);
        if (distance) {
            let direction = (px as IBaseInputState).direction || this.renderer!.activeCamera!.unproject(px.x, px.y);
            return direction.scaleTo(distance).addA(this.renderer!.activeCamera!.eye);
        }
    }

    /**
     * Returns geodetic coordinates on the current terrain planet by its screen coordinates.
     * position or null if input coordinates is outside the planet.
     * @public
     * @param {Vec2 | IBaseInputState} px - Pixel screen 2d coordinates.
     * @returns {LonLat | undefined} -
     */
    public getLonLatFromPixelTerrain(px: Vec2 | IBaseInputState): LonLat | undefined {
        let coords = this.getCartesianFromPixelTerrain(px);
        if (coords) {
            return this.ellipsoid.cartesianToLonLat(coords);
        }
    }

    /**
     * Project cartesian coordinates to screen space.
     * @public
     * @param {Vec3} coords - Cartesian coordinates.
     * @returns {Vec2} - Screen coordinates.
     */
    public getPixelFromCartesian(coords: Vec3): Vec2 {
        return this.renderer!.activeCamera!.project3v(coords);
    }

    /**
     * Project geodetic coordinates to screen space.
     * @public
     * @param {LonLat} lonlat - Geodetic coordinates.
     * @returns {Vec2 | undefined} - Screen coordinates.
     */
    public getPixelFromLonLat(lonlat: LonLat): Vec2 | undefined {
        let coords = this.ellipsoid.lonLatToCartesian(lonlat);
        if (coords) {
            return this.renderer!.activeCamera!.project3v(coords);
        }
    }

    /**
     * Returns distance from an active (screen) camera to the planet ellipsoid.
     * @public
     * @param {Vec2} px - Screen coordinates.
     * @returns {number} -
     */
    public getDistanceFromPixelEllipsoid(px: Vec2 | IBaseInputState): number | undefined {
        let coords = this.getCartesianFromPixelEllipsoid(px);
        if (coords) {
            return coords.distance(this.renderer!.activeCamera!.eye);
        }
    }

    /**
     * Returns distance from active (screen) camera to the planet terrain by screen coordinates.
     * @public
     * @param {Vec2 | IBaseInputState} px - Screen coordinates.
     * @returns {number | undefined} -
     */
    public getDistanceFromPixel(px: Vec2 | IBaseInputState): number {
        // if (this.terrain!.isEmpty) {
        //     return this.getDistanceFromPixelEllipsoid(px) || 0;
        // } else {
        return this.renderer!.getDistanceFromPixel(px) || this.getDistanceFromPixelEllipsoid(px) || 0;
        //}
    }

    /**
     * Sets camera to the planet geographical extent.
     * @public
     * @param {Extent} extent - Geographical extent.
     */
    public viewExtent(extent: Extent) {
        if (this.camera) {
            this.camera.viewExtent(extent);
        } else {
            this._initialViewExtent = extent;
        }
    }

    /**
     * Fits camera position for the view extent.
     * @public
     * @param {Array.<number>} extentArr - Geographical extent array, (exactly 4 entries)
     * where index 0 - southwest longitude, 1 - latitude southwest, 2 - longitude northeast, 3 - latitude northeast.
     */
    public viewExtentArr(extentArr: NumberArray4) {
        this.viewExtent(new Extent(new LonLat(extentArr[0], extentArr[1]), new LonLat(extentArr[2], extentArr[3])));
    }

    /**
     * Gets current camera view extent.
     * @public
     * @returns {Extent} -
     */
    public getExtent(): Extent {
        if (this.renderer) {

            let w = this.renderer.handler.getWidth(),
                h = this.renderer.handler.getHeight();

            let extent = [
                this.getLonLatFromPixelTerrain(new Vec2(0, 0)),
                this.getLonLatFromPixelTerrain(new Vec2(w, 0)),
                this.getLonLatFromPixelTerrain(new Vec2(w, h)),
                this.getLonLatFromPixelTerrain(new Vec2(0, h))
            ];

            if (extent[0] && extent[1] && extent[2] && extent[3]) {

                let min_lon = extent[0].lon, min_lat = extent[2].lat,
                    max_lon = extent[1].lon, max_lat = extent[0].lat;

                for (let i = 0; i < extent.length; i++) {
                    if (extent[i]!.lon > max_lon) max_lon = extent[i]!.lon;
                    if (extent[i]!.lat > max_lat) max_lat = extent[i]!.lat;
                    if (extent[i]!.lon < min_lon) min_lon = extent[i]!.lon;
                    if (extent[i]!.lat < min_lat) min_lat = extent[i]!.lat;
                }

                return new Extent(new LonLat(min_lon, min_lat), new LonLat(max_lon, max_lat));
            }
        }

        return this.quadTreeStrategy._viewExtent;
    }

    public getViewExtent(): Extent {
        return this.quadTreeStrategy._viewExtent;
    }

    /**
     * Sets camera to the planet geographical position.
     * @public
     * @param {LonLat} lonlat - Camera position.
     * @param {LonLat} [lookLonLat] - Viewpoint.
     * @param {Vec3} [up] - Camera up vector.
     */
    public viewLonLat(lonlat: LonLat, lookLonLat?: LonLat, up?: Vec3) {
        this.camera.setLonLat(lonlat, lookLonLat, up);
    }

    /**
     * Fly active camera to the view extent.
     * @public
     * @param {Extent} extent - Geographical extent.
     * @param {Number} [height] - Height on the end of the flight route.
     * @param {IPlanetFlyCartesianParams} params - Flight parameters.
     */
    public flyExtent(
        extent: Extent,
        height?: number,
        params: IPlanetFlyCartesianParams = {}
    ) {
        this.camera.flyExtent(extent, height, params);
    }

    /**
     * Fly camera to the point.
     * @public
     * @param {Vec3} cartesian - Fly cartesian coordinates.
     * @param {IPlanetFlyCartesianParams} params - Flight parameters.
     */
    public flyCartesian(cartesian: Vec3, params?: IPlanetFlyCartesianParams) {
        this.camera.flyCartesian(cartesian, params);
    }

    /**
     * Fly camera to the geodetic position.
     * @public
     * @param {LonLat} lonlat - Fly geographical coordinates.
     * @param {IPlanetFlyCartesianParams} params - Flight parameters.
     */
    public flyLonLat(
        lonlat: LonLat,
        params: IPlanetFlyCartesianParams = {}
    ) {
        this.camera.flyLonLat(lonlat, params);
    }

    /**
     * Stop current flight.
     * @public
     */
    public stopFlying() {
        this.camera.stopFlying();
    }

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

        let readyCollections: Record<number, boolean> = {};
        for (let i = 0; i < this._layers.length; i++) {
            let li = this._layers[i];
            if (li instanceof Vector) {
                (li as Vector).each(function (e: Entity) {
                    if (e._entityCollection && !readyCollections[e._entityCollection.id]) {
                        e._entityCollection.billboardHandler.refreshTexCoordsArr();
                        readyCollections[e._entityCollection.id] = true;
                    }
                });
            }
        }
    }

    public getEntityTerrainPoint(entity: Entity, res: Vec3) {
        let n = this.quadTreeStrategy._renderedNodes, i = n.length;
        while (i--) {
            if (n[i].segment.isEntityInside(entity)) {
                return n[i].segment.getEntityTerrainPoint(entity, res);
            }
        }
    }

    public async getHeightDefault(lonLat: LonLat): Promise<number> {
        return new Promise<number>((resolve: (alt: number) => void) => {
            if (this.terrain) {
                this.terrain.getHeightAsync(lonLat.clone(), (alt: number) => {
                    resolve(alt);
                });
            } else {
                resolve(0);
            }
        });
    }

    public async getHeightAboveELL(lonLat: LonLat): Promise<number> {
        return new Promise<number>((resolve: (alt: number) => void) => {
            if (this.terrain) {
                this.terrain.getHeightAsync(lonLat.clone(), (alt: number) => {
                    resolve(alt + this.terrain!.geoid.getHeightLonLat(lonLat));
                });
            } else {
                resolve(0);
            }
        });
    }

    public override onremove() {
        this.memClear();
        this.quadTreeStrategy.destroyBranches();
        this.quadTreeStrategy.clearRenderedNodes();
    }

    // function checkTerrainCollision(entity) {
    //     let _tempTerrPoint = new Vec3();
    //     let nodes = globus.planet._renderedNodes;
    //     for (let j = 0; j < nodes.length; j++) {
    //         let seg = nodes[j].segment;
    //         if (seg && seg._extentLonLat.isInside(entity.getLonLat())) {
    //             seg.getEntityTerrainPoint(entity, _tempTerrPoint);
    //             entity.setCartesian3v(_tempTerrPoint);
    //             break;
    //         }
    //     }
    // }

}

const PLANET_EVENTS: PlanetEventsList = [
    /**
     * Triggered before globe frame begins to render.
     * @event og.scene.Planet#draw
     */
    "draw",

    /**
     * Triggered when layer has added to the planet.
     * @event og.scene.Planet#layeradd
     */
    "layeradd",

    /**
     * Triggered when base layer changed.
     * @event og.scene.Planet#baselayerchange
     */
    "baselayerchange",

    /**
     * Triggered when layer has removed from the planet.
     * @event og.scene.Planet#layerremove
     */
    "layerremove",

    /**
     * Triggered when some layer visibility changed.
     * @event og.scene.Planet#layervisibilitychange
     */
    "layervisibilitychange",

    /**
     * Triggered when all data is loaded
     * @event og.scene.Planet#rendercompleted
     */
    "rendercompleted",

    /**
     * Triggered when all data is loaded
     * @event og.scene.Planet#terraincompleted
     */
    "terraincompleted",

    /**
     * Triggered when layer data is laded
     * @event og.scene.Planet#terraincompleted
     */
    "layerloadend"
];