/**
* @module og/scene/Planet
*/
"use strict";
import * as utils from "../utils/shared.js";
import * as shaders from "../shaders/drawnode.js";
import * as math from "../math.js";
import * as mercator from "../mercator.js";
import * as quadTree from "../quadTree/quadTree.js";
import * as segmentHelper from "../segment/segmentHelper.js";
import { decodeFloatFromRGBAArr } from "../math/coder.js";
import { Extent } from "../Extent.js";
import { Framebuffer } from "../webgl/Framebuffer.js";
import { GeoImageCreator } from "../utils/GeoImageCreator.js";
import { Quat, Vec3, Vec4 } from "../math/index.js";
import { Vector } from "../layer/Vector.js";
import { Loader } from "../utils/Loader.js";
import { Key, Lock } from "../Lock.js";
import { LonLat } from "../LonLat.js";
import { Node } from "../quadTree/Node.js";
import { NormalMapCreator } from "../utils/NormalMapCreator.js";
import { PlanetCamera } from "../camera/PlanetCamera.js";
import { RenderNode } from "./RenderNode.js";
import { Segment } from "../segment/Segment.js";
import { SegmentLonLat } from "../segment/SegmentLonLat.js";
import { PlainSegmentWorker } from "../utils/PlainSegmentWorker.js";
import { TerrainWorker } from "../utils/TerrainWorker.js";
import { VectorTileCreator } from "../utils/VectorTileCreator.js";
import { wgs84 } from "../ellipsoid/wgs84.js";
import { NIGHT, SPECULAR } from "../res/images.js";
import { Geoid } from "../terrain/Geoid.js";
import { isUndef } from "../utils/shared.js";
import { MAX_RENDERED_NODES } from "../quadTree/quadTree.js";
const CUR_LOD_SIZE = 250; //px
const MIN_LOD_SIZE = 312; //px
const MAX_LOD_SIZE = 190; //px
let _tempPickingPix_ = new Uint8Array(4),
_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;
const EVENT_NAMES = [
/**
* 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"
];
/**
* 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 bacame the same zoom level
* @param {Number} [options.minEqualZoomAltitude=10000.0] - Minimal altitude since segments on the screen bacame the same zoom level
* @param {Number} [options.minEqualZoomCameraSlope=0.8] - Minimal camera slope above te globe where segments on the screen bacame the same zoom level
* @fires og.scene.Planet#draw
* @fires og.scene.Planet#layeradd
* @fires og.scene.Planet#baselayerchange
* @fires og.scene.Planet#layerremove
* @fires og.scene.Planet#layervisibilitychange
* @fires og.scene.Planet#geoimageadd
*/
export class Planet extends RenderNode {
constructor(options = {}) {
super(options.name);
this._cameraFrustums = options.frustums || [
[1, 100 + 0.075],
[100, 1000 + 0.075],
[1000, 1e6 + 10000],
[1e6, 1e9]
];
/**
* @public
* @type {Ellipsoid}
*/
this.ellipsoid = options.ellipsoid || wgs84;
/**
* @public
* @type {Boolean}
*/
this.lightEnabled = true;
/**
* Squared ellipsoid radius.
* @protected
* @type {number}
*/
this._planetRadius2 = this.ellipsoid.getPolarSize() * this.ellipsoid.getPolarSize();
/**
* All layers array.
* @public
* @type {Array.<Layer>}
*/
this.layers = [];
/**
* Current visible imagery tile layers array.
* @public
* @type {Array.<Layer>}
*/
this.visibleTileLayers = [];
/**
* Current visible vector layers array.
* @protected
* @type {Array.<layer.Vector>}
*/
this.visibleVectorLayers = [];
this._visibleTileLayerSlices = [];
/**
* Vector layers visible nodes with collections.
* @protected
* @type {Array.<EntityCollection>}
*/
this._frustumEntityCollections = [];
/**
* There is only one base layer on the globe when layer.isBaseLayer is true.
* @public
* @type {Layer}
*/
this.baseLayer = null;
/**
* Terrain provider.
* @public
* @type {Terrain}
*/
this.terrain = null;
/**
* Camera is this.renderer.activeCamera pointer.
* @public
* @type {PlanetCamera}
*/
this.camera = null;
this._minAltitude = options.minAltitude;
this._maxAltitude = options.maxAltitude;
this.maxEqualZoomAltitude = options.maxEqualZoomAltitude || 15000000.0;
this.minEqualZoomAltitude = options.minEqualZoomAltitude || 10000.0;
this.minEqualZoomCameraSlope = options.minEqualZoomCameraSlope || 0.8;
/**
* Screen mouse pointer projected to planet cartesian position.
* @public
* @type {Vec3}
*/
this.mousePositionOnEarth = new Vec3();
this.emptyTexture = null;
this.transparentTexture = null;
this.defaultTexture = null;
/**
* Current visible minimal zoom index planet segment.
* @public
* @type {number}
*/
this.minCurrZoom = math.MAX;
/**
* Current visible maximal zoom index planet segment.
* @public
* @type {number}
*/
this.maxCurrZoom = math.MIN;
this._viewExtent = null;
/**
* @protected
*/
this._createdNodesCount = 0;
/**
* Planet's segments collected for rendering frame.
* @protected
* @type {quadTree.Node}
*/
this._renderedNodes = [];
this._renderedNodesInFrustum = [];
/**
* Created nodes cache
* @protected
* @type {quadTree.Node}
*/
this._quadTreeNodesCacheMerc = {};
/**
* Current visible mercator segments tree nodes array.
* @protected
* @type {quadTree.Node}
*/
this._visibleNodes = {};
/**
* Current visible north pole nodes tree nodes array.
* @protected
* @type {quadTree.Node}
*/
this._visibleNodesNorth = {};
/**
* Current visible south pole nodes tree nodes array.
* @protected
* @type {quadTree.Node}
*/
this._visibleNodesSouth = {};
/**
* Layers activity lock.
* @public
* @type {idle.Lock}
*/
this.layerLock = new Lock();
/**
* Terrain providers activity lock.
* @public
* @type {idle.Lock}
*/
this.terrainLock = new Lock();
/**
* Layer's transparent colors.
* @protected
*/
this._tcolorArr = [];
/**
* Height scale factor. 1 - is normal elevation scale.
* @protected
* @type {number}
*/
this._heightFactor = 1.0;
/**
* Precomputed indexes array for differrent grid size segments.
* @protected
* @type {Array.<Array.<number>>}
*/
this._indexesCache = [];
this._indexesCacheToRemove = [];
this._indexesCacheToRemoveCounter = 0;
/**
* Precomputed texture coordinates buffers for differrent grid size segments.
* @protected
* @type {Array.<Array.<number>>}
*/
this._textureCoordsBufferCache = [];
/**
* Framebuffer for relief. Is null when WEBGL_draw_buffers extension initialized.
* @protected
* @type {Object}
*/
this._heightPickingFramebuffer = null;
/**
* Mercator grid tree.
* @protected
* @type {quadTree.Node}
*/
this._quadTree = null;
/**
* North grid tree.
* @protected
* @type {quadTree.Node}
*/
this._quadTreeNorth = null;
/**
* South grid tree.
* @protected
* @type {quadTree.Node}
*/
this._quadTreeSouth = null;
/**
* Night glowing gl texture.
* @protected
*/
this._nightTexture = null;
/**
* Specular mask gl texture.
* @protected
*/
this._specularTexture = null;
//TODO: replace to a function
let a = utils.createColorRGB(options.ambient, new Vec3(0.2, 0.2, 0.2));
let d = utils.createColorRGB(options.diffuse, new Vec3(0.8, 0.8, 0.8));
let s = utils.createColorRGB(options.specular, new Vec3(0.0003, 0.0003, 0.0003));
let shininess = options.shininess || 20.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]);
/**
* True for rendering night glowing texture.
* @protected
* @type {boolean}
*/
this._useNightTexture = isUndef(options.useNightTexture) ? true : options.useNightTexture;
/**
* True for rendering specular mask texture.
* @protected
* @type {boolean}
*/
this._useSpecularTexture = isUndef(options.useSpecularTexture)
? true
: options.useSpecularTexture;
this._maxGridSize = Math.log2(options.maxGridSize || 128);
/**
* Segment multiple textures size.(4 - convinient value for the most devices)
* @const
* @public
*/
this.SLICE_SIZE = 4;
this.SLICE_SIZE_4 = this.SLICE_SIZE * 4;
this.SLICE_SIZE_3 = this.SLICE_SIZE * 3;
/**
* Level of the visible segment detalization.
* @public
* @type {number}
*/
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);
/**
* GeoImage creator.
* @protected
* @type{utils.GeoImageCreator}
*/
this._geoImageCreator = null;
this._vectorTileCreator = null;
this._normalMapCreator = null;
this._terrainWorker = new TerrainWorker(3);
this._plainSegmentWorker = new PlainSegmentWorker(3);
this._tileLoader = new Loader(options.loadingBatchSize || 12);
this._memKey = new Key();
this.events.registerNames(EVENT_NAMES);
this._distBeforeMemClear = 0.0;
this._prevCamEye = new Vec3();
this._initialized = false;
this.always = [];
this._renderCompleted = false;
}
static getBearingNorthRotationQuat(cartesian) {
let n = cartesian.normal();
let t = Vec3.proj_b_to_plane(Vec3.UNIT_Y, n);
return Quat.getLookRotation(t, n);
}
/**
* Add the given control to the renderer of the planet scene.
* @param {control.Control} control - Control.
*/
addControl(control) {
control.planet = this;
control.addTo(this.renderer);
}
get lodSize() {
return this._lodSize;
}
setLodSize(currentLodSize, minLodSize, maxLodSize) {
this._maxLodSize = maxLodSize || this._maxLodSize;
this._minLodSize = minLodSize || this._minLodSize;
this._curLodSize = currentLodSize || this._curLodSize;
this._renderCompletedActivated = false;
}
/**
* Add the given controls array to the renderer of the planet.
* @param {Array.<control.Control>} cArr - Control array.
*/
addControls(cArr) {
for (var 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} -
*/
getLayerByName(name) {
var i = this.layers.length;
while (i--) {
if (this.layers[i].name === name) {
return this.layers[i];
}
}
}
/**
* Adds the given layer to the planet.
* @param {Layer} layer - Layer object.
* @public
*/
addLayer(layer) {
layer.addTo(this);
}
/**
* Dispatch layer visibility changing event.
* @param {Layer} layer - Changed layer.
* @protected
*/
_onLayerVisibilityChanged(layer) {
this.events.dispatch(this.events.layervisibilitychange, layer);
}
/**
* Adds the given layers array to the planet.
* @param {Array.<Layer>} layers - Layers array.
* @public
*/
addLayers(layers) {
for (var i = 0; i < layers.length; i++) {
this.addLayer(layers[i]);
}
}
/**
* Removes the given layer from the planet.
* @param {Layer} layer - Layer to remove.
* @return {Layer|undefined} The removed layer or undefined if the layer was not found.
* @public
*/
removeLayer(layer) {
return layer.remove();
}
/**
*
* @protected
* @param {Layer} layer - Material layer.
*/
_clearLayerMaterial(layer) {
var lid = layer._id;
this._quadTree.traverseTree(function (node) {
var mats = node.segment.materials;
if (mats[lid]) {
mats[lid].clear();
mats[lid] = null;
}
});
}
/**
* Get the collection of layers associated with this planet.
* @return {Array.<Layer>} Layers array.
* @public
*/
getLayers() {
return this.layers;
}
/**
* Sets base layer coverage to the planet.
* @param {Layer} layer - Layer object.
* @public
*/
setBaseLayer(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.
*/
setHeightFactor(factor) {
this._renderCompletedActivated = false;
if (this._heightFactor !== factor) {
this._heightFactor = factor;
this._quadTree.destroyBranches();
}
}
/**
* Gets elevation scale.
* @returns {number} Terrain elevation scale
*/
getHeightFactor() {
return this._heightFactor;
}
/**
* Sets terrain provider
* @public
* @param {Terrain} terrain - Terrain provider.
*/
setTerrain(terrain) {
this._renderCompletedActivated = 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._normalMapCreator && this._normalMapCreator.setBlur(terrain.blur != undefined ? terrain.blur : true);
if (this._quadTree) {
this._quadTree.destroyBranches();
}
if (terrain._geoid) {
if (!terrain._geoid.model) {
terrain._geoid.model = null;
Geoid.loadModel(terrain._geoid.src)
.then((m) => {
terrain.getGeoid().setModel(m);
this._plainSegmentWorker.setGeoid(terrain.getGeoid());
})
.catch((err) => {
console.log(err);
});
} else {
this._plainSegmentWorker.setGeoid(terrain.getGeoid());
}
}
}
/**
* @virtual
* @protected
*/
_initializeShaders() {
var h = this.renderer.handler;
h.addProgram(shaders.drawnode_screen_nl(), true);
//h.addProgram(shaders.drawnode_screen_wl(), true);
h.addProgram(shaders.drawnode_screen_wl_webgl2(), 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._heightPickingFramebuffer = new Framebuffer(this.renderer.handler, {
width: 320,
height: 240
});
this._heightPickingFramebuffer.init();
this.renderer.screenTexture.height = this._heightPickingFramebuffer.textures[0];
}
/**
* @virtual
* @public
*/
init() {
// 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 (var i = 0; i <= TABLESIZE; i++) {
!this._indexesCache[i] && (this._indexesCache[i] = new Array(TABLESIZE));
for (var j = 0; j <= TABLESIZE; j++) {
!this._indexesCache[i][j] && (this._indexesCache[i][j] = new Array(TABLESIZE));
for (var k = 0; k <= TABLESIZE; k++) {
!this._indexesCache[i][j][k] &&
(this._indexesCache[i][j][k] = new Array(TABLESIZE));
for (var m = 0; m <= TABLESIZE; m++) {
!this._indexesCache[i][j][k][m] &&
(this._indexesCache[i][j][k][m] = new Array(TABLESIZE));
for (var q = 0; q <= TABLESIZE; q++) {
let ptr = {
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
);
indexes = null;
} else {
this._indexesCacheToRemove[kk++] = ptr;
}
this._indexesCache[i][j][k][m][q] = ptr;
}
}
}
}
}
this.renderer.events.on("resize", () => {
this._renderCompletedActivated = false;
});
// 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)
);
}
texCoordCache = null;
// creating empty textures
var that = this;
this.renderer.handler.createDefaultTexture(null, function (t) {
that.solidTextureOne = t;
that.solidTextureTwo = t;
});
this.transparentTexture = this.renderer.handler.transparentTexture;
this.camera = this.renderer.activeCamera = new PlanetCamera(this, {
frustums: this._cameraFrustums,
eye: new Vec3(0, 0, 28000000),
look: new Vec3(0, 0, 0),
up: new Vec3(0, 1, 0),
minAltitude: this._minAltitude,
maxAltitude: this._maxAltitude
});
this.camera.update();
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._quadTree = new Node(Segment, this, quadTree.NW, null, 0, 0,
Extent.createFromArray([-20037508.34, -20037508.34, 20037508.34, 20037508.34])
);
this._quadTreeNorth = new Node(SegmentLonLat, this, quadTree.NW, null, 0, 0,
Extent.createFromArray([-180, mercator.MAX_LAT, 180, 90])
);
this._quadTreeSouth = new Node(SegmentLonLat, this, quadTree.NW, null, 0, 0,
Extent.createFromArray([-180, -90, 180, mercator.MIN_LAT])
);
this.drawMode = this.renderer.handler.gl.TRIANGLE_STRIP;
// Applying shaders
this._initializeShaders();
this.updateVisibleLayers();
this.renderer.addPickingCallback(this, this._frustumEntityCollectionPickingCallback);
// loading Earth night glowing texture
if (this._useNightTexture) {
createImageBitmap(NIGHT).then(
(e) => (this._nightTexture = this.renderer.handler.createTextureDefault(e))
);
}
// load water specular mask
if (this._useSpecularTexture) {
createImageBitmap(SPECULAR).then(
(e) => (this._specularTexture = this.renderer.handler.createTexture_l(e))
);
}
this._geoImageCreator = new GeoImageCreator(this);
this._vectorTileCreator = new VectorTileCreator(this);
this._normalMapCreator = new NormalMapCreator(this, {
minTableSize: 1,
maxTableSize: TABLESIZE,
blur: this.terrain && (this.terrain.blur != undefined ? this.terrain.blur : true)
});
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._initialized = true;
this.renderer.events.on("postdraw", () => {
this._checkRendercompleted();
});
}
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);
ci.buffer = null;
}
}
_preRender() {
// Zoom 0
this._quadTree.createChildrenNodes();
this._quadTree.segment.createPlainSegment();
// Zoom 1
for (let i = 0; i < this._quadTree.nodes.length; i++) {
this._quadTree.nodes[i].segment.createPlainSegment();
}
// same for poles
this._quadTreeNorth.createChildrenNodes();
this._quadTreeNorth.segment.createPlainSegment();
for (let i = 0; i < this._quadTreeNorth.nodes.length; i++) {
this._quadTreeNorth.nodes[i].segment.createPlainSegment();
}
this._quadTreeSouth.createChildrenNodes();
this._quadTreeSouth.segment.createPlainSegment();
for (let i = 0; i < this._quadTreeSouth.nodes.length; i++) {
this._quadTreeSouth.nodes[i].segment.createPlainSegment();
}
this._preLoad();
}
_preLoad() {
this._clearRenderedNodeList();
this._skipPreRender = false;
// Same for poles
this._quadTreeNorth.segment.passReady = true;
this._quadTreeNorth.renderNode(true);
this._normalMapCreator.drawSingle(this._quadTreeNorth.segment);
for (let i = 0; i < this._quadTreeNorth.nodes.length; i++) {
this._quadTreeNorth.nodes[i].segment.passReady = true;
this._quadTreeNorth.nodes[i].renderNode(true);
this._normalMapCreator.drawSingle(this._quadTreeNorth.nodes[i].segment);
}
this._quadTreeSouth.segment.passReady = true;
this._quadTreeSouth.renderNode(true);
this._normalMapCreator.drawSingle(this._quadTreeSouth.segment);
for (let i = 0; i < this._quadTreeSouth.nodes.length; i++) {
this._quadTreeSouth.nodes[i].segment.passReady = true;
this._quadTreeSouth.nodes[i].renderNode(true);
this._normalMapCreator.drawSingle(this._quadTreeSouth.nodes[i].segment);
}
// Zoom 1
for (let i = 0; i < this._quadTree.nodes.length; i++) {
this._quadTree.nodes[i].segment.passReady = true;
this._quadTree.nodes[i].renderNode(true);
this._normalMapCreator.drawSingle(this._quadTree.nodes[i].segment);
}
// Zoom 0
this._quadTree.segment.passReady = true;
this._quadTree.renderNode(true);
this._normalMapCreator.drawSingle(this._quadTree.segment);
}
/**
* Creates default textures first for nirth pole and whole globe and second for south pole.
* @public
* @param{Object} param0 -
* @param{Object} param1 -
*/
createDefaultTextures(param0, param1) {
this.renderer.handler.gl.deleteTexture(this.solidTextureOne);
this.renderer.handler.gl.deleteTexture(this.solidTextureTwo);
var that = this;
this.renderer.handler.createDefaultTexture(param0, function (t) {
that.solidTextureOne = t;
});
this.renderer.handler.createDefaultTexture(param1, function (t) {
that.solidTextureTwo = t;
});
}
/**
* Updates attribution lists
* @public
*/
updateAttributionsList() {
var html = "";
for (var i = 0; i < this.layers.length; i++) {
var li = this.layers[i];
if (li._visibility) {
if (li._attribution.length) {
html += "<li>" + li._attribution + "</li>";
}
}
}
if (this.renderer) {
if (html.length) {
this.renderer.div.attributions.style.display = "block";
this.renderer.div.attributions.innerHTML = "<ul>" + html + "</ul>";
} else {
this.renderer.div.attributions.style.display = "none";
this.renderer.div.attributions.innerHTML = "";
}
}
}
/**
* Updates visible layers.
* @public
*/
updateVisibleLayers() {
this.visibleTileLayers = [];
this.visibleTileLayers.length = 0;
this.visibleVectorLayers = [];
this.visibleVectorLayers.length = 0;
var html = "";
for (var i = 0; i < this.layers.length; i++) {
var li = this.layers[i];
if (li._visibility) {
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._attribution.length) {
html += "<li>" + li._attribution + "</li>";
}
} else if (li._fading && li._fadingOpacity > 0) {
if (li.hasImageryTiles()) {
this.visibleTileLayers.push(li);
}
if (li.isVector) {
this.visibleVectorLayers.push(li);
}
}
}
if (this.renderer) {
if (html.length) {
this.renderer.div.attributions.style.display = "block";
this.renderer.div.attributions.innerHTML = "<ul>" + html + "</ul>";
} else {
this.renderer.div.attributions.style.display = "none";
this.renderer.div.attributions.innerHTML = "";
}
}
this._sortLayers();
}
/**
* Sort visible layer - preparing for rendering.
* @protected
*/
_sortLayers() {
this.visibleVectorLayers.sort(function (a, b) {
return a._zIndex - b._zIndex || a._height - b._height;
});
this._visibleTileLayerSlices = [];
this._visibleTileLayerSlices.length = 0;
if (this.visibleTileLayers.length) {
this.visibleTileLayers.sort(function (a, b) {
return a._height - b._height || a._zIndex - b._zIndex;
});
var k = -1;
var currHeight = this.visibleTileLayers[0]._height;
for (var i = 0; i < this.visibleTileLayers.length; i++) {
if (i % this.SLICE_SIZE === 0 || this.visibleTileLayers[i]._height !== currHeight) {
k++;
this._visibleTileLayerSlices[k] = [];
currHeight = this.visibleTileLayers[i]._height;
}
this._visibleTileLayerSlices[k].push(this.visibleTileLayers[i]);
}
}
}
_clearRenderedNodeList() {
// clearing all node list
this._renderedNodes.length = 0;
this._renderedNodes = [];
// clearing nodes in frustums
for (let i = 0, len = this._renderedNodesInFrustum.length; i < len; i++) {
this._renderedNodesInFrustum[i].length = 0;
this._renderedNodesInFrustum[i] = [];
}
}
/**
* Collects visible quad nodes.
* @protected
*/
_collectRenderNodes() {
let cam = this.camera;
this._lodSize = math.lerp(cam.slope < 0.0 ? 0.0 : cam.slope, this._curLodSize, this._minLodSize);
cam._insideSegment = null;
// clear first
this._clearRenderedNodeList();
this._viewExtent = null;
this._visibleNodes = {};
this._visibleNodesNorth = {};
this._visibleNodesSouth = {};
this.minCurrZoom = math.MAX;
this.maxCurrZoom = math.MIN;
this._quadTree.renderTree(cam, 0, null);
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._renderedNodes = [];
// clearing nodes in frustums
for (let i = 0, len = this._renderedNodesInFrustum.length; i < len; i++) {
this._renderedNodesInFrustum[i].length = 0;
this._renderedNodesInFrustum[i] = [];
}
for (var i = 0, len = temp.length; i < len; i++) {
var 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);
}
}
this._quadTreeNorth.renderTree(cam, 0, null);
this._quadTreeSouth.renderTree(cam, 0, null);
}
_globalPreDraw() {
let cam = this.camera;
this._distBeforeMemClear += this._prevCamEye.distance(cam.eye);
this._prevCamEye.copy(cam.eye);
cam.checkFly();
// free memory
if (this._createdNodesCount > MAX_NODES && this._distBeforeMemClear > 1000.0) {
this.terrain.clearCache();
this.memClear();
}
if (this._indexesCacheToRemoveCounter > 600) {
this.clearIndexesCache();
}
}
/**
* Render node callback.
* @public
*/
frame() {
this._renderScreenNodesPASS();
this._renderHeightPickingFramebufferPASS();
this.drawEntityCollections(this._frustumEntityCollections);
}
_checkRendercompleted() {
if (this._renderCompleted) {
if (!this._renderCompletedActivated) {
this._renderCompletedActivated = true;
this.events.dispatch(this.events.rendercompleted, true);
}
} else {
this._renderCompletedActivated = false;
}
this._renderCompleted = true;
}
/**
* @protected
*/
_renderScreenNodesPASS() {
let sh, shu;
let renderer = this.renderer;
let h = renderer.handler;
let gl = h.gl;
let cam = renderer.activeCamera;
let frustumIndex = cam.getCurrentFrustum();
gl.disable(gl.POLYGON_OFFSET_FILL);
if (frustumIndex === cam.FARTHEST_FRUSTUM_INDEX) {
if (this._skipPreRender/* && (!this._renderCompletedActivated || cam.isMoved)*/) {
this._collectRenderNodes();
}
this._skipPreRender = true;
// 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);
this.transformLights();
this._normalMapCreator.frame();
// Creating geoImages textures.
this._geoImageCreator.frame();
// Collect entity collections from vector layers
this._collectVectorLayerCollections();
// Vector tiles rasteriazation
this._vectorTileCreator.frame();
}
gl.enable(gl.CULL_FACE);
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
if (this.lightEnabled) {
h.programs.drawnode_screen_wl.activate();
sh = h.programs.drawnode_screen_wl._program;
shu = sh.uniforms;
gl.uniform4fv(shu.lightsPositions, this._lightsTransformedPositions);
gl.uniformMatrix3fv(shu.normalMatrix, false, cam.getNormalMatrix());
gl.uniformMatrix4fv(shu.viewMatrix, false, cam.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, cam.getProjectionMatrix());
gl.uniform3fv(shu.diffuse, this._diffuse);
gl.uniform3fv(shu.ambient, this._ambient);
gl.uniform4fv(shu.specular, this._specular);
// bind night glowing material
gl.activeTexture(gl.TEXTURE0 + this.SLICE_SIZE);
gl.bindTexture(gl.TEXTURE_2D,
(this.camera._lonLat.height > 329958.0 && (this._nightTexture || this.transparentTexture)) || this.transparentTexture
);
gl.uniform1i(shu.nightTexture, this.SLICE_SIZE);
// bind specular material
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);
} 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);
// drawing planet nodes
var rn = this._renderedNodesInFrustum[frustumIndex],
sl = this._visibleTileLayerSlices;
if (sl.length) {
let sli = sl[0];
for (var i = sli.length - 1; i >= 0; --i) {
let li = sli[i];
if (li._fading && li._refreshFadingOpacity()) {
sli.splice(i, 1);
}
}
}
let isEq = this.terrain.equalizeVertices;
i = rn.length;
while (i--) {
let s = rn[i].segment;
isEq && s.equalize();
s.readyToEngage && s.engage();
s.screenRendering(sh, sl[0], 0);
}
//gl.enable(gl.POLYGON_OFFSET_FILL);
for (let j = 1, len = sl.length; j < len; j++) {
let slj = sl[j];
for (i = slj.length - 1; i >= 0; --i) {
let li = slj[i];
if (li._fading && li._refreshFadingOpacity()) {
slj.splice(i, 1);
}
}
i = rn.length;
//gl.polygonOffset(0, -j);
while (i--) {
rn[i].segment.screenRendering(sh, sl[j], j, this.transparentTexture, true);
}
}
gl.disable(gl.BLEND);
}
/**
* @protected
*/
_renderHeightPickingFramebufferPASS() {
if (!this.terrain.isEmpty) {
this._heightPickingFramebuffer.activate();
let sh;
let renderer = this.renderer;
let h = renderer.handler;
let gl = h.gl;
let cam = renderer.activeCamera;
let frustumIndex = cam.getCurrentFrustum();
if (frustumIndex === cam.FARTHEST_FRUSTUM_INDEX) {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
} else {
gl.clear(gl.DEPTH_BUFFER_BIT);
}
h.programs.drawnode_heightPicking.activate();
sh = h.programs.drawnode_heightPicking._program;
let shu = sh.uniforms;
gl.uniformMatrix4fv(shu.viewMatrix, false, renderer.activeCamera.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, renderer.activeCamera.getProjectionMatrix());
gl.uniform3fv(shu.eyePositionHigh, cam.eyeHigh);
gl.uniform3fv(shu.eyePositionLow, cam.eyeLow);
// drawing planet nodes
var rn = this._renderedNodesInFrustum[frustumIndex],
sl = this._visibleTileLayerSlices;
let i = rn.length;
while (i--) {
rn[i].segment.heightPickingRendering(sh, sl[0]);
}
this._heightPickingFramebuffer.deactivate();
}
}
/**
* @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.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
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
var rn = this._renderedNodesInFrustum[cam.getCurrentFrustum()],
sl = this._visibleTileLayerSlices;
let i = rn.length;
while (i--) {
rn[i].segment.colorPickingRendering(sh, sl[0], 0);
}
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);
gl.disable(gl.BLEND);
}
/**
* @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
var rn = this._renderedNodesInFrustum[cam.getCurrentFrustum()],
sl = this._visibleTileLayerSlices;
let i = rn.length;
while (i--) {
rn[i].segment.depthRendering(sh, sl[0]);
}
}
_collectVectorLayerCollections() {
this._frustumEntityCollections.length = 0;
this._frustumEntityCollections = [];
var i = this.visibleVectorLayers.length;
while (i--) {
let vi = this.visibleVectorLayers[i];
if (vi._fading && vi._refreshFadingOpacity()) {
this.visibleVectorLayers.splice(i, 1);
}
vi.collectVisibleCollections(this._frustumEntityCollections);
vi.update();
}
}
/**
* Vector layers picking pass.
* @protected
*/
_frustumEntityCollectionPickingCallback() {
this.drawPickingEntityCollections(this._frustumEntityCollections);
}
/**
* Starts clear memory thread.
* @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.abort();
var that = this;
// setTimeout(function () {
that._quadTree.clearTree();
that._quadTreeNorth.clearTree();
that._quadTreeSouth.clearTree();
that.layerLock.free(that._memKey);
that.terrainLock.free(that._memKey);
that._normalMapCreator.free(that._memKey);
// }, 0);
this._createdNodesCount = 0;
}
/**
* Returns ray vector hit ellipsoid coordinates.
* If the ray doesn't hit ellipsoit returns null.
* @public
* @param {Ray} ray - Ray 3d.
* @returns {Vec3} -
*/
getRayIntersectionEllipsoid(ray) {
return this.ellipsoid.hitRay(ray.origin, ray.direction);
}
/**
* Returns 2d screen coordanates projection point to the planet ellipsoid 3d coordinates.
* @public
* @param {math.Pixel} px - 2D sreen coordinates.
* @returns {Vec3} -
*/
getCartesianFromPixelEllipsoid(px) {
var cam = this.renderer.activeCamera;
return this.ellipsoid.hitRay(cam.eye, cam.unproject(px.x, px.y));
}
/**
* Returns 2d screen coordanates projection point to the planet ellipsoid geographical coordinates.
* @public
* @param {math.Pixel} px - 2D screen coordinates.
* @returns {LonLat} -
*/
getLonLatFromPixelEllipsoid(px) {
var coords = this.getCartesianFromPixelEllipsoid(px);
if (coords) {
return this.ellipsoid.cartesianToLonLat(coords);
}
return null;
}
/**
* Returns 3d cartesian coordinates on the relief planet by mouse cursor
* position or null if mouse cursor is outside the planet.
* @public
* @returns {Vec3} -
*/
getCartesianFromMouseTerrain() {
var ms = this.renderer.events.mouseState;
var distance = this.getDistanceFromPixel(ms);
if (distance) {
return ms.direction.scaleTo(distance).addA(this.renderer.activeCamera.eye);
}
return null;
}
/**
* Returns 3d cartesian coordinates on the relief planet by 2d screen coordinates.
* position or null if input coordinates is outside the planet.
* @public
* @param {Vec2} px - Pixel screen 2d coordinates.
* @param {Boolean} [force=false] - Force framebuffer rendering.
* @returns {Vec3} -
*/
getCartesianFromPixelTerrain(px) {
var distance = this.getDistanceFromPixel(px);
if (distance) {
var direction = px.direction || this.renderer.activeCamera.unproject(px.x, px.y);
return direction.scaleTo(distance).addA(this.renderer.activeCamera.eye);
}
return null;
}
/**
* Returns geographical coordinates on the relief planet by 2d screen coordinates.
* position or null if input coordinates is outside the planet.
* @public
* @param {Vec2} px - Pixel screen 2d coordinates.
* @param {Boolean} [force=false] - Force framebuffer rendering.
* @returns {LonLat} -
*/
getLonLatFromPixelTerrain(px, force) {
var coords = this.getCartesianFromPixelTerrain(px, force);
if (coords) {
return this.ellipsoid.cartesianToLonLat(coords);
}
return null;
}
/**
* Returns projected 2d screen coordinates by 3d cartesian coordiantes.
* @public
* @param {Vec3} coords - Cartesian coordinates.
* @returns {Vec2} -
*/
getPixelFromCartesian(coords) {
return this.renderer.activeCamera.project(coords);
}
/**
* Returns projected 2d screen coordinates by geographical coordinates.
* @public
* @param {LonLat} lonlat - Geographical coordinates.
* @returns {Vec2} -
*/
getPixelFromLonLat(lonlat) {
var coords = this.ellipsoid.lonLatToCartesian(lonlat);
if (coords) {
return this.renderer.activeCamera.project(coords);
}
return null;
}
/**
* Returns distance from active camera to the the planet ellipsoid
* coordiantes unprojected by 2d screen coordiantes, or null if screen coordinates outside the planet.
* @public
* @param {Vec2} px - Screen coordinates.
* @returns {number} -
*/
getDistanceFromPixelEllipsoid(px) {
var coords = this.getCartesianFromPixelEllipsoid(px);
return coords ? coords.distance(this.renderer.activeCamera.eye) : null;
}
/**
* Returns distance from active camera to the the relief planet coordiantes unprojected
* by 2d screen coordiantes, or null if screen coordinates outside the planet.
* If screen coordinates inside the planet but relief is not exists in the
* point than function returns distance to the planet ellipsoid.
* @public
* @param {Vec2} px - Screen coordinates.
* @param {Boolean} [force=false] - Force framebuffer rendering.
* @returns {number} -
*/
getDistanceFromPixel(px) {
if (this.terrain.isEmpty) {
return this.getDistanceFromPixelEllipsoid(px) || 0;
} else {
let r = this.renderer,
cnv = this.renderer.handler.canvas;
let spx = px.x / cnv.width,
spy = (cnv.height - px.y) / cnv.height;
_tempPickingPix_[0] = _tempPickingPix_[1] = _tempPickingPix_[2] = 0.0;
let dist = 0;
// HEIGHT
this._heightPickingFramebuffer.activate();
if (this._heightPickingFramebuffer.isComplete()) {
this._heightPickingFramebuffer.readPixels(_tempPickingPix_, spx, spy);
dist = decodeFloatFromRGBAArr(_tempPickingPix_);
}
this._heightPickingFramebuffer.deactivate();
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.direction || this.renderer.activeCamera.unproject(px.x, px.y);
dist = -(viewPosition.z / viewPosition.w) / dir.dot(this.renderer.activeCamera.getForward());
}
r.screenDepthFramebuffer.deactivate();
}
return dist;
}
}
/**
* Sets camera to the planet geographical extent.
* @public
* @param {Extent} extent - Geographical extent.
*/
viewExtent(extent) {
this.renderer.activeCamera.viewExtent(extent);
}
/**
* Sets camera to the planet geographical 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.
*/
viewExtentArr(extentArr) {
this.renderer.activeCamera.viewExtent(
new Extent(
new LonLat(extentArr[0], extentArr[1]),
new LonLat(extentArr[2], extentArr[3])
)
);
}
/**
* Gets current viewing geographical extent.
* @public
* @returns {Extent} -
*/
getViewExtent() {
return this._viewExtent;
// if (this._viewExtentMerc) {
// var ne = this._viewExtentMerc.northEast.inverseMercator(),
// sw = this._viewExtentMerc.southWest.inverseMercator();
// if (this._viewExtentWGS84) {
// var e = this._viewExtentWGS84;
// if (e.northEast.lon > ne.lon) {
// ne.lon = e.northEast.lon;
// }
// if (e.northEast.lat > ne.lat) {
// ne.lat = e.northEast.lat;
// }
// if (e.southWest.lon < sw.lon) {
// sw.lon = e.southWest.lon;
// }
// if (e.southWest.lat < sw.lat) {
// sw.lat = e.southWest.lat;
// }
// }
// return new Extent(sw, ne);
// } else if (this._viewExtentWGS84) {
// return this._viewExtentWGS84;
// }
}
/**
* Sets camera to the planet geographical position.
* @public
* @param {LonLat} lonlat - New geographical position.
* @param {Vec3} [up] - Camera UP vector.
*/
viewLonLat(lonlat, up) {
this.renderer.activeCamera.setLonLat(lonlat, up);
}
/**
* Fly camera to the planet geographical 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 {cameraCallback} [startCallback] - Callback that calls after flying when flying is finished.
* @param {cameraCallback} [completeCallback] - Callback that calls befor the flying begins.
*/
flyExtent(extent, height, up, ampl, completeCallback, startCallback) {
this.renderer.activeCamera.flyExtent(
extent,
height,
up,
ampl,
completeCallback,
startCallback
);
}
/**
* Fly camera to the new point.
* @public
* @param {Vec3} cartesian - Fly coordiantes.
* @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 [completeCallback]
* @param [startCallback]
* @param [frameCallback]
*/
flyCartesian(cartesian, look, up, ampl, completeCallback, startCallback, frameCallback) {
this.renderer.activeCamera.flyCartesian(
cartesian,
look,
up,
ampl,
completeCallback,
startCallback,
frameCallback
);
}
/**
* Fly camera to the new geographical position.
* @public
* @param {LonLat} lonlat - Fly geographical coordiantes.
* @param {Vec3} [look] - Camera "look at" point on the end of a flying.
* @param {Vec3} [up] - Camera UP vector on the end of a flying.
* @param {Number} [ampl] - Altitude amplitude factor.
* @param [completeCallback]
* @param [startCallback]
* @param [frameCallback]
*/
flyLonLat(lonlat, look, up, ampl, completeCallback, startCallback, frameCallback) {
this.renderer.activeCamera.flyLonLat(
lonlat,
look,
up,
ampl,
completeCallback,
startCallback,
frameCallback
);
}
/**
* Breaks the flight.
* @public
*/
stopFlying() {
this.renderer.activeCamera.stopFlying();
}
updateBillboardsTexCoords() {
for (let i = 0; i < this.entityCollections.length; i++) {
this.entityCollections[i].billboardHandler.refreshTexCoordsArr();
}
let readyCollections = {};
for (let i = 0; i < this.layers.length; i++) {
let li = this.layers[i];
if (li instanceof Vector) {
li.each(function (e) {
if (e._entityCollection && !readyCollections[e._entityCollection.id]) {
e._entityCollection.billboardHandler.refreshTexCoordsArr();
readyCollections[e._entityCollection.id] = true;
}
});
}
}
}
getEntityTerrainPoint(entity, res) {
let n = this._renderedNodes,
i = n.length;
while (i--) {
if (n[i].segment.isEntityInside(entity)) {
return n[i].segment.getEntityTerrainPoint(entity, res);
}
}
}
}