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"
];