import * as math from "../math";
import * as segmentHelper from "../segment/segmentHelper";
import * as shaders from "../shaders/drawnode";
import * as utils from "../utils/shared";
import {Atmosphere, IAtmosphereParams} from "../control/Atmosphere";
import {Control} from "../control/Control";
import {createColorRGB, isUndef} from "../utils/shared";
import {createEvents, EventsHandler} from "../Events";
import {decodeFloatFromRGBAArr} from "../math/coder";
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, GeoidModel} from "../terrain/Geoid";
import {GeoImageCreator} from "../utils/GeoImageCreator";
import {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 {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, Vec4, NumberArray2, NumberArray3, NumberArray4} from "../math/index";
import {Vector} from "../layer/Vector";
import {VectorTileCreator} from "../utils/VectorTileCreator";
import {wgs84} from "../ellipsoid/wgs84";
import {WebGLBufferExt, WebGLTextureExt, IDefaultTextureParams, WebGLContextExt} from "../webgl/Handler";
import {Program} from "../webgl/Program";
import {Segment} from "../segment/Segment";
import {AtmosphereParameters} from "../shaders/atmos";
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;
}
export type PlanetEventsList = [
"draw",
"layeradd",
"baselayerchange",
"layerremove",
"layervisibilitychange",
"rendercompleted",
"terraincompleted",
"layerloadend"
];
const CUR_LOD_SIZE = 250; //px
const MIN_LOD_SIZE = 312; //px
const MAX_LOD_SIZE = 190; //px
let _tempPickingPix_ = new Uint8Array(4);
let _tempDepthColor_ = new Uint8Array(4);
const DEPTH_DISTANCE = 11;//m
/**
* Maximum created nodes count. The more nodes count the more memory usage.
* @const
* @type {number}
* @default
*/
const MAX_NODES = 200;
const HORIZON_TANGENT = 0.81;
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
* @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 _updateLayer: 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: Layer[];
protected _visibleTileLayerSlices: Layer[][];
/**
* Vector layers visible nodes with collections.
* @protected
* @type {Array.<EntityCollection>}
*/
protected _frustumEntityCollections: 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;
public maxEqualZoomAltitude: number;
public minEqualZoomAltitude: number;
public minEqualZoomCameraSlope: number;
/**
* 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;
/**
* Current visible minimal zoom index planet segment.
* @public
* @type {number}
*/
public minCurrZoom: number;
/**
* Current visible maximal zoom index planet segment.
* @public
* @type {number}
*/
public maxCurrZoom: number;
public _viewExtent: Extent;
protected _initialViewExtent: Extent | null;
public _createdNodesCount: number;
/**
* Planet's segments collected for rendering frame.
* @public
* @type {Node}
*/
public _renderedNodes: Node[];
public _renderedNodesInFrustum: Node[][];
public _fadingNodes: Map<number, Node>;
public _fadingNodesInFrustum: Node[][];
/**
* 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 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;
/**
* Level of details of visible segments.
* @protected
* @type {number}
*/
protected _lodSize: number;
protected _curLodSize: number;
protected _minLodSize: number;
public _maxLodSize: 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 always: any[];
public _renderCompleted: boolean
public _renderCompletedActivated: boolean;
public _terrainCompleted: boolean;
public _terrainCompletedActivated: 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 _skipPreRender: boolean;
protected _nightTextureSrc: string | null;
protected _specularTextureSrc: string | null;
public transitionTime: number;
public _prevNodes: Map<number, Node>;
public _currNodes: Map<number, Node>;
protected _transitionOpacityEnabled: boolean;
protected _atmosphere: Atmosphere;
private _minDistanceBeforeMemClear: number = 0;
constructor(options: IPlanetParams = {}) {
super(options.name);
this._atmosphere = new Atmosphere(options.atmosphereParameters);
this._prevNodes = new Map<number, Node>();
this._currNodes = new Map<number, Node>();
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._updateLayer = false;
this.visibleTileLayers = [];
this.visibleVectorLayers = [];
this._visibleTileLayerSlices = [];
this._frustumEntityCollections = [];
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.maxEqualZoomAltitude = options.maxEqualZoomAltitude || 15000000.0;
this.minEqualZoomAltitude = options.minEqualZoomAltitude || 10000.0;
this.minEqualZoomCameraSlope = options.minEqualZoomCameraSlope || 0.8;
this.mousePositionOnEarth = new Vec3();
this.emptyTexture = null;
this.transparentTexture = null;
this.defaultTexture = null;
this.minCurrZoom = math.MAX;
this.maxCurrZoom = math.MIN;
this._viewExtent = new Extent(new LonLat(180, 180), new LonLat(-180, -180));
this._skipPreRender = false;
this._initialViewExtent = null;
this._createdNodesCount = 0;
this._renderedNodes = [];
this._renderedNodesInFrustum = [];
this._fadingNodes = new Map<number, Node>;
this._fadingNodesInFrustum = [];
this.layerLock = new Lock();
this.terrainLock = new Lock();
this._heightFactor = 1.0;
this._indexesCache = [];
this._indexesCacheToRemove = [];
this._indexesCacheToRemoveCounter = 0;
this._textureCoordsBufferCache = [];
this.quadTreeStrategy = options.quadTreeStrategyPrototype ? new options.quadTreeStrategyPrototype(this) : new EarthQuadTreeStrategy(this);
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._lodSize = CUR_LOD_SIZE;
this._curLodSize = CUR_LOD_SIZE;
this._minLodSize = MIN_LOD_SIZE;
this._maxLodSize = MAX_LOD_SIZE;
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.always = [];
this._renderCompleted = false;
this._renderCompletedActivated = false;
this._terrainCompleted = false;
this._terrainCompletedActivated = 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([1.0, 0.41]);
this.solidTextureOne = null;
this.solidTextureTwo = null;
this._nightTextureSrc = options.nightTextureSrc || null;
this._specularTextureSrc = options.specularTextureSrc || null;
this._transitionOpacityEnabled = options.transitionOpacityEnabled != undefined ? options.transitionOpacityEnabled : true;
}
/**
* Returns true if current terrain data set is loaded
*/
public get terrainReady(): boolean {
return this._terrainCompleted && this._terrainCompletedActivated;
}
public get maxGridSize(): number {
return this._maxGridSize;
}
public getNorthFrameRotation(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];
}
/**
* @todo: remove after tests
* Get the collection of layers associated with this planet.
* @return {Array.<Layer>} Layers array.
* @public
*/
public getLayers(): Layer[] {
return this.layers;
}
public get sunPos(): Vec3 {
return (this.renderer!.controls.sun as 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!);
}
public get lodSize(): number {
return this._lodSize;
}
public setLodSize(currentLodSize: number, minLodSize?: number, maxLodSize?: number) {
this._maxLodSize = maxLodSize || this._maxLodSize;
this._minLodSize = minLodSize || this._minLodSize;
this._curLodSize = currentLodSize;
this._renderCompletedActivated = false;
this._terrainCompletedActivated = false;
}
/**
* 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) {
this._renderCompletedActivated = false;
this._terrainCompletedActivated = false;
if (this._heightFactor !== factor) {
this._heightFactor = factor;
this.quadTreeStrategy.destroyBranches();
this._clearRenderedNodeList();
this._clearRenderNodesInFrustum();
}
}
/**
* 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) {
this._renderCompletedActivated = false;
this._terrainCompletedActivated = false;
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), true);
} 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), true);
} else {
h.addProgram(shaders.drawnode_screen_wl_webgl1NoAtmos(), true);
}
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(), true);
} else {
h.addProgram(shaders.drawnode_screen_wl_webgl1NoAtmos(), true);
}
}
}
protected _initializeShaders() {
let h = this.renderer!.handler;
h.addProgram(shaders.drawnode_screen_nl(), true);
h.addProgram(shaders.drawnode_colorPicking(), true);
h.addProgram(shaders.drawnode_depth(), true);
h.addProgram(shaders.drawnode_heightPicking(), true);
this.renderer!.addPickingCallback(this, this._renderColorPickingFramebufferPASS);
this.renderer!.addDepthCallback(this, this._renderDepthFramebufferPASS);
this.renderer!.addDistanceCallback(this, this._renderDistanceFramebufferPASS);
}
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("resize", () => {
this._renderCompletedActivated = false;
this._terrainCompletedActivated = false;
});
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._renderedNodesInFrustum = new Array(this.camera.frustums.length);
for (let i = 0, len = this._renderedNodesInFrustum.length; i < len; i++) {
this._renderedNodesInFrustum[i] = [];
}
// Creating quad trees nodes
this.quadTreeStrategy.init();
this.drawMode = this.renderer!.handler.gl!.TRIANGLE_STRIP;
// Applying shaders
this._initializeShaders();
this._initializeAtmosphere();
this._updateVisibleLayers();
this.renderer!.addPickingCallback(this, this._frustumEntityCollectionPickingCallback);
// 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);
// Loading first nodes for better viewing if you have started on a lower altitude.
this._preRender();
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.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;
}
}
protected _preRender() {
this.quadTreeStrategy.preRender();
this._preLoad();
}
protected _preLoad() {
this._clearRenderedNodeList();
this._skipPreRender = false;
this.quadTreeStrategy.preLoad();
}
/**
* 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._updateLayer = 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);
}
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);
}
}
}
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()));
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 _clearRenderedNodeList() {
this._renderedNodes.length = 0;
this._renderedNodes = [];
}
protected _clearRenderNodesInFrustum() {
for (let i = 0, len = this._renderedNodesInFrustum.length; i < len; i++) {
this._renderedNodesInFrustum[i].length = 0;
this._renderedNodesInFrustum[i] = [];
}
}
protected _collectRenderedNodesMaxZoom(cam: PlanetCamera) {
if (cam.slope > this.minEqualZoomCameraSlope && cam._lonLat.height < this.maxEqualZoomAltitude && cam._lonLat.height > this.minEqualZoomAltitude) {
this.minCurrZoom = this.maxCurrZoom;
let temp = this._renderedNodes,
rf = this._renderedNodesInFrustum,
temp2 = [];
this._clearRenderNodesInFrustum();
this._renderedNodes = [];
for (let i = 0, len = temp.length; i < len; i++) {
let ri = temp[i];
let ht = ri.segment.centerNormal.dot(cam._b);
if (ri.segment.tileZoom === this.maxCurrZoom || ht < HORIZON_TANGENT) {
this._renderedNodes.push(ri);
let k = 0, inFrustum = ri.inFrustum;
while (inFrustum) {
if (inFrustum & 1) {
rf[k].push(ri);
}
k++;
inFrustum >>= 1;
}
} else {
temp2.push(ri);
}
}
for (let i = 0, len = temp2.length; i < len; i++) {
temp2[i].renderTree(cam, this.maxCurrZoom, null, false, temp2[i]);
}
}
}
public set transitionOpacityEnabled(isEnabled: boolean) {
this._transitionOpacityEnabled = isEnabled;
//@todo: set render nodes transition opacity to one
}
public get transitionOpacityEnabled(): boolean {
return this._transitionOpacityEnabled;
}
/**
* Collects visible quad nodes.
* @protected
*/
protected _collectRenderNodes(cam: PlanetCamera) {
this._lodSize = math.lerp(cam.slope < 0.0 ? 0.0 : cam.slope, this._curLodSize, this._minLodSize);
cam._insideSegment = null;
// clear first
this._clearRenderedNodeList();
this._clearRenderNodesInFrustum();
this._viewExtent.southWest.set(180, 180);
this._viewExtent.northEast.set(-180, -180);
// todo: replace to camera
this.minCurrZoom = math.MAX;
this.maxCurrZoom = math.MIN;
this.quadTreeStrategy.collectRenderNodes();
this._collectRenderedNodesMaxZoom(cam);
// main camera effect
this._fadingNodes.clear();
if (this._transitionOpacityEnabled) {
let opaqueNodes: Node[] = [];
for (let i = 0; i < this._renderedNodes.length; i++) {
let ri = this._renderedNodes[i];
// it's not impossible to move the code into Node.addToRender, because
// we can't know actual state before _collectRenderedNodesMaxZoom pass
ri._collectFadingNodes();
ri._refreshTransitionOpacity();
if (ri.segment._transitionOpacity >= 1.0) {
ri.clearNeighbors();
ri.getRenderedNodesNeighbors(opaqueNodes);
opaqueNodes.push(ri);
} else {
for (let j = 0; j < ri._fadingNodes.length; j++) {
let rij = ri._fadingNodes[j];
if (rij.segment && rij.segment._transitionOpacity >= 1.0) {
rij.clearNeighbors();
rij.getRenderedNodesNeighbors(opaqueNodes);
opaqueNodes.push(rij);
}
}
}
}
}
}
protected _renderScreenNodesPASSNoAtmos() {
let cam = this.renderer!.activeCamera as PlanetCamera;
let sh = this._setUniformsNoAtmos(cam);
//
// PASS 0: rendering base slice of layers, which is often zero height
this._renderingScreenNodes(sh, cam, this._renderedNodesInFrustum[cam.currentFrustumIndex]);
}
protected _renderScreenNodesPASSAtmos() {
let cam = this.renderer!.activeCamera as PlanetCamera;
let sh = this._setUniformsAtmos(cam);
//
// PASS 0: rendering base slice of layers, which is often zero height
this._renderingScreenNodes(sh, cam, this._renderedNodesInFrustum[cam.currentFrustumIndex]);
}
protected _renderScreenNodesWithHeightPASSNoAtmos() {
let cam = this.renderer!.activeCamera as PlanetCamera;
let sh = this._setUniformsNoAtmos(cam);
//
// PASS 1: rendering slices, and layers with heights, without transition opacity effect
this._renderingScreenNodesWithHeight(sh, cam, this._renderedNodesInFrustum[cam.currentFrustumIndex]);
}
protected _renderScreenNodesWithHeightPASSAtmos() {
let cam = this.renderer!.activeCamera as PlanetCamera;
let sh = this._setUniformsAtmos(cam);
//
// PASS 1: rendering slices, and layers with heights, without transition opacity effect
this._renderingScreenNodesWithHeight(sh, cam, this._renderedNodesInFrustum[cam.currentFrustumIndex]);
}
protected _globalPreDraw() {
let cam = this.camera;
// Might be it's better to replace it in setTerrain,
// but we have to be sure that setTerrain exists with renderer insttance
this.renderer!.__useDistanceFramebuffer__ = !this.terrain!.isEmpty;
this._distBeforeMemClear += this._prevCamEye.distance(cam.eye);
this._prevCamEye.copy(cam.eye);
cam.checkFly();
// free memory
if (this._createdNodesCount > MAX_NODES && this._distBeforeMemClear > this._minDistanceBeforeMemClear) {
this.terrain!.clearCache();
this.memClear();
}
if (this._indexesCacheToRemoveCounter > 600) {
this._clearIndexesCache();
}
}
/**
* Render node callback.
* @public
*/
public override preFrame() {
if (this._updateLayer) {
this._updateLayer = false;
this._updateVisibleLayers();
}
if (this.camera.isFirstPass) {
this.camera.update();
if (this._skipPreRender && this._collectRenderNodesIsActive) {
this._collectRenderNodes(this.camera);
}
this._skipPreRender = true;
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();
}
this.drawEntityCollections(this._frustumEntityCollections);
}
/**
* Render node callback.
* Frame function is called for each renderer activrCamera frustum.
* @public
* @override
*/
public override frame() {
this._renderScreenNodesPASS();
}
protected _checkRendercompleted() {
if (this._renderCompleted) {
if (!this._renderCompletedActivated) {
this._renderCompletedActivated = true;
this.events.dispatch(this.events.rendercompleted, true);
}
} else {
this._renderCompletedActivated = false;
}
this._renderCompleted = true;
if (this._terrainCompleted) {
if (!this._terrainCompletedActivated) {
this._terrainCompletedActivated = true;
this.events.dispatch(this.events.terraincompleted, true);
}
} else {
this._terrainCompletedActivated = false;
}
this._terrainCompleted = true;
}
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.lightsPositions, this._lightsPositions);
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.lightsPositions, this._lightsPositions);
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 = (nodes: Map<number, boolean>, sh: Program, currentNode: Node, sl: Layer[], sliceIndex: number, outTransparentSegments?: 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 (this._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);
} else {
f.screenRendering(sh, sl, sliceIndex, this.transparentTexture, true);
}
}
}
}
}
protected _renderingFadingNodesNoDepth = (nodes: Map<number, boolean>, sh: Program, currentNode: Node, sl: Layer[], sliceIndex: number) => {
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 (this._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);
} else {
f.screenRendering(sh, sl, sliceIndex, this.transparentTexture, true);
}
}
}
gl.enable(gl.DEPTH_TEST);
}
/**
* Drawing nodes
*/
protected _renderingScreenNodes(sh: Program, cam: PlanetCamera, renderedNodes: Node[]) {
let firstPass = cam.isFirstPass;
let sl = this._visibleTileLayerSlices;
if (sl.length) {
let sli = sl[0];
for (let i = sli.length - 1; i >= 0; --i) {
let li = sli[i];
if (li._fading && firstPass && li._refreshFadingOpacity()) {
sli.splice(i, 1);
}
}
}
let nodes = new Map<number, boolean>;
let transparentSegments: Segment[] = [];
let isEq = this.terrain!.equalizeVertices;
let i = renderedNodes.length;
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(nodes, sh, ri, sl[0], 0);
isEq && s.equalize();
s.readyToEngage && s.engage();
s.screenRendering(sh, sl[0], 0);
}
} else {
while (i--) {
let ri = renderedNodes[i];
let s = ri.segment;
this._renderingFadingNodes(nodes, sh, ri, sl[0], 0, transparentSegments);
if (s._transitionOpacity < 1) {
transparentSegments.push(s);
} else {
isEq && s.equalize();
s.readyToEngage && s.engage();
s.screenRendering(sh, sl[0], 0);
}
}
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(sh: Program, cam: PlanetCamera, renderedNodes: Node[]) {
let gl = this.renderer!.handler.gl!;
let firstPass = cam.isFirstPass;
let sl = this._visibleTileLayerSlices;
gl.enable(gl.POLYGON_OFFSET_FILL);
gl.disable(gl.CULL_FACE);
let nodes = new Map<number, boolean>;
let transparentSegments: Segment[] = [];
for (let j = 1, len = sl.length; j < len; j++) {
let slj = sl[j];
for (let i = slj.length - 1; i >= 0; --i) {
let li = slj[i];
if (li._fading && firstPass && li._refreshFadingOpacity()) {
slj.splice(i, 1);
}
}
gl.polygonOffset(0, -j);
let i = renderedNodes.length;
while (i--) {
let ri = renderedNodes[i];
this._renderingFadingNodes(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 _renderDistanceFramebufferPASS() {
if (!this.terrain!.isEmpty) {
let sh;
let renderer = this.renderer!;
let h = renderer.handler;
let gl = h.gl!;
let cam = renderer.activeCamera as PlanetCamera;
h.programs.drawnode_heightPicking.activate();
sh = h.programs.drawnode_heightPicking._program;
let 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);
// drawing planet nodes
let rn = this._renderedNodesInFrustum[cam.currentFrustumIndex];
let sl = this._visibleTileLayerSlices;
let i = rn.length;
while (i--) {
rn[i].segment.heightPickingRendering(sh, sl[0]);
}
}
}
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._renderedNodesInFrustum[cam.getCurrentFrustum()];
let sl = this._visibleTileLayerSlices;
let i = rn.length;
while (i--) {
rn[i].segment.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);
}
protected _renderDepthFramebufferPASS() {
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;
let cam = renderer.activeCamera!;
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.uniform3fv(shu.frustumPickingColor, cam.frustum._pickingColorU);
// drawing planet nodes
let rn = this._renderedNodesInFrustum[cam.getCurrentFrustum()],
sl = this._visibleTileLayerSlices;
let i = rn.length;
while (i--) {
rn[i].segment.depthRendering(sh, sl[0]);
}
gl.enable(gl.BLEND);
}
protected _collectVectorLayerCollections() {
this._frustumEntityCollections.length = 0;
this._frustumEntityCollections = [];
let i = this.visibleVectorLayers.length;
while (i--) {
let vi = this.visibleVectorLayers[i] as Vector;
if (vi._fading && vi._refreshFadingOpacity()) {
this.visibleVectorLayers.splice(i, 1);
}
vi.collectVisibleCollections(this._frustumEntityCollections);
vi.update();
}
}
protected _frustumEntityCollectionPickingCallback() {
this.drawPickingEntityCollections(this._frustumEntityCollections);
}
/**
* 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!.project(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!.project(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 {
let r = this.renderer!;
let cnv = r.handler!.canvas!;
let spx = px.x / cnv.width;
let spy = (cnv.height - px.y) / cnv.height;
_tempPickingPix_[0] = _tempPickingPix_[1] = _tempPickingPix_[2] = 0.0;
let dist = 0;
r.readDistanceColor(spx, spy, _tempPickingPix_);
dist = decodeFloatFromRGBAArr(_tempPickingPix_);
if (!(_tempPickingPix_[0] || _tempPickingPix_[1] || _tempPickingPix_[2])) {
dist = this.getDistanceFromPixelEllipsoid(px) || 0;
} else if (dist < DEPTH_DISTANCE) {
r.screenDepthFramebuffer!.activate();
//if (r.screenDepthFramebuffer.isComplete()) {
r.screenDepthFramebuffer!.readPixels(_tempDepthColor_, spx, spy);
let screenPos = new Vec4(spx * 2.0 - 1.0, spy * 2.0 - 1.0, (_tempDepthColor_[0] / 255.0) * 2.0 - 1.0, 1.0 * 2.0 - 1.0);
let viewPosition = this.camera.frustums[0].inverseProjectionMatrix.mulVec4(screenPos);
let dir = (px as IBaseInputState).direction || r.activeCamera!.unproject(px.x, px.y);
dist = -(viewPosition.z / viewPosition.w) / dir.dot(r.activeCamera!.getForward());
//}
r.screenDepthFramebuffer!.deactivate();
}
return dist;
}
}
/**
* 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 getViewExtent(): Extent {
return this._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 {Vec3} [up] - Camera UP vector on the end of a flying.
* @param {Number} [ampl] - Altitude amplitude factor.
* @param {Function} [startCallback] - Callback that calls before the flying begins.
* @param {Function} [completeCallback] - Callback that calls after flying when flying is finished.
*/
public flyExtent(
extent: Extent,
height?: number,
up?: Vec3,
ampl?: number,
completeCallback?: Function,
startCallback?: Function
) {
this.camera.flyExtent(extent, height, up, ampl, completeCallback, startCallback);
}
/**
* Fly camera to the point.
* @public
* @param {Vec3} cartesian - Point coordinates.
* @param {Vec3} [look] - Camera "look at" point.
* @param {Vec3} [up] - Camera UP vector on the end of a flying.
* @param {Number} [ampl] - Altitude amplitude factor.
* @param {Function} [completeCallback] - Call the function in the end of flight
* @param {Function} [startCallback] - Call the function in the beginning
* @param {Function} [frameCallback] - Each frame callback
*/
public flyCartesian(
cartesian: Vec3,
look?: Vec3 | null,
up?: Vec3 | null,
ampl?: number,
completeCallback?: Function | null,
startCallback?: Function | null,
frameCallback?: Function | null
) {
this.camera.flyCartesian(cartesian, look, up, ampl, completeCallback, startCallback, frameCallback);
}
/**
* Fly camera to the geodetic position.
* @public
* @param {LonLat} lonlat - Fly geographical coordinates.
* @param {Vec3 | LonLat} [look] - Camera viewpoint in the end of the flight.
* @param {Vec3} [up] - Camera UP vector on the end of a flying.
* @param {Number} [ampl] - Altitude amplitude factor.
* @param [completeCallback]
* @param [startCallback]
* @param [frameCallback]
*/
public flyLonLat(
lonlat: LonLat,
look?: Vec3 | LonLat,
up?: Vec3,
ampl?: number,
completeCallback?: Function,
startCallback?: Function,
frameCallback?: Function
) {
this.camera.flyLonLat(lonlat, look, up, ampl, completeCallback, startCallback, frameCallback);
}
/**
* 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._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._renderedNodes = [];
}
// 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"
];