import * as mercator from "../mercator";
import {doubleToTwoFloats2} from "../math/coder";
import {Extent} from "../Extent";
import type {EventCallback, EventsHandler} from "../Events";
import {Layer} from "./Layer";
import type {LayerEventsList, ILayerParams} from "./Layer";
import {LonLat} from "../LonLat";
import {Material} from "./Material";
import type {NumberArray2} from "../math/Vec2";
import type {NumberArray4} from "../math/Vec4";
import {Planet} from "../scene/Planet";
import type {WebGLBufferExt, WebGLTextureExt} from "../webgl/Handler";
export interface IBaseGeoImageParams extends ILayerParams {
fullExtent?: boolean;
corners?: NumberArray2[];
}
type BaseGeoImageEventsList = [
"loadend"
];
const BASEGEOIMAGE_EVENTS: BaseGeoImageEventsList = [
/**
* Triggered when image data is loaded
* @event EventsHandler<BaseGeoImageEventsList>#loadend
*/
"loadend"
];
export type BaseGeoImageEventsType = EventsHandler<BaseGeoImageEventsList> & EventsHandler<LayerEventsList>;
/**
* BaseGeoImage layer represents square imagery layer that
* could be a static image, or animated video or webgl buffer
* object displayed on the globe.
* @class
* @extends {Layer}
*/
class BaseGeoImage extends Layer {
public override events: BaseGeoImageEventsType;
protected _projType: number;
protected _frameWidth: number;
protected _frameHeight: number;
protected _sourceReady: boolean;
protected _sourceTexture: WebGLTextureExt | null;
protected _materialTexture: WebGLTextureExt | null;
protected _gridBufferLow: WebGLBufferExt | null;
protected _gridBufferHigh: WebGLBufferExt | null;
protected _extentWgs84ParamsHigh: Float32Array;
protected _extentWgs84ParamsLow: Float32Array;
protected _extentMercParamsHigh: Float32Array;
protected _extentMercParamsLow: Float32Array;
protected _refreshFrame: boolean;
protected _frameCreated: boolean;
protected _sourceCreated: boolean;
public _animate: boolean;
protected _ready: boolean;
public _creationProceeding: boolean;
public _isRendering: boolean;
protected _extentWgs84: Extent;
protected _cornersWgs84: LonLat[];
protected _cornersMerc: LonLat[];
protected _isFullExtent: number;
/**
* rendering function pointer
* @type {Function}
*/
public rendering: Function;
protected _onLoadend_: EventCallback | null;
constructor(name: string | null, options: IBaseGeoImageParams = {}) {
super(name, options);
// @ts-ignore
this.events = this.events.registerNames(BASEGEOIMAGE_EVENTS);
this._projType = 0;
this._frameWidth = 256;
this._frameHeight = 256;
this._sourceReady = false;
this._sourceTexture = null;
this._materialTexture = null;
this._gridBufferLow = null;
this._gridBufferHigh = null;
this._extentWgs84ParamsHigh = new Float32Array(4);
this._extentWgs84ParamsLow = new Float32Array(4);
this._extentMercParamsHigh = new Float32Array(4);
this._extentMercParamsLow = new Float32Array(4);
this._refreshFrame = true;
this._frameCreated = false;
this._sourceCreated = false;
this._animate = false;
this._ready = false;
this._creationProceeding = false;
this._isRendering = false;
this._extentWgs84 = new Extent();
this._cornersWgs84 = [];
this._cornersMerc = [];
this._isFullExtent = options.fullExtent ? 1 : 0;
/**
* rendering function pointer
*/
this.rendering = this._renderingProjType0.bind(this);
this._onLoadend_ = null;
options.corners && this.setCorners(options.corners);
}
public override get isIdle(): boolean {
return super.isIdle && this._ready;
}
public override addTo(planet: Planet) {
this._onLoadend_ = this._onLoadend.bind(this);
this.events.on("loadend", this._onLoadend_, this);
return super.addTo(planet);
}
protected _onLoadend() {
if (this._planet) {
this._planet.events.dispatch(this._planet.events.layerloadend, this);
}
}
public override remove() {
this.events.off("loadend", this._onLoadend_);
this._onLoadend_ = null;
return super.remove();
}
public override get instanceName(): string {
return "BaseGeoImage";
}
/**
* Gets corners coordinates.
* @public
* @return {Array.<LonLat>} - (exactly 4 entries)
*/
public getCornersLonLat(): LonLat[] {
let c = this._cornersWgs84;
return [
new LonLat(c[0].lon, c[0].lat),
new LonLat(c[1].lon, c[1].lat),
new LonLat(c[2].lon, c[2].lat),
new LonLat(c[3].lon, c[3].lat)
];
}
/**
* Gets corners coordinates.
* @public
* @return {Array.<Array<number>>} - (exactly 3 entries)
*/
public getCorners(): NumberArray2[] {
let c = this._cornersWgs84;
return [
[c[0].lon, c[0].lat],
[c[1].lon, c[1].lat],
[c[2].lon, c[2].lat],
[c[3].lon, c[3].lat]
];
}
/**
* Sets geoImage geographical corners coordinates.
* @public
* @param {Array.<Array.<number>>} corners - GeoImage corners coordinates. Where first coordinate (exactly 3 entries)
* coincedents to the left top image corner, secont to the right top image corner, third to the right bottom
* and fourth - left bottom image corner.
*/
public setCorners(corners: NumberArray2[]) {
this.setCornersLonLat(LonLat.join(corners));
}
/**
* Sets geoImage geographical corners coordinates.
* @public
* @param {Array.<LonLat>} corners - GeoImage corners coordinates. Where first coordinate
* coincedents to the left top image corner, secont to the right top image corner, third to the right bottom
* and fourth - left bottom image corner. (exactly 4 entries)
*/
public setCornersLonLat(corners: LonLat[]) {
this._refreshFrame = true;
this._cornersWgs84 = [
corners[0].clone(),
corners[1].clone(),
corners[2].clone(),
corners[3].clone()
];
for (let i = 0; i < this._cornersWgs84.length; i++) {
if (this._cornersWgs84[i].lat >= 89.9) {
this._cornersWgs84[i].lat = 89.9;
}
if (this._cornersWgs84[i].lat <= -89.9) {
this._cornersWgs84[i].lat = -89.9;
}
}
this._extent.setByCoordinates(this._cornersWgs84);
let me = this._extent;
if (me.southWest.lat > mercator.MAX_LAT || me.northEast.lat < mercator.MIN_LAT) {
this._projType = 0;
this.rendering = this._renderingProjType0;
} else {
this._projType = 1;
this.rendering = this._renderingProjType1;
}
if (this._ready && !this._creationProceeding) {
this._planet!._geoImageCreator.add(this);
}
}
/**
* Creates geoImage frame.
* @protected
*/
protected _createFrame() {
this._extentWgs84 = this._extent.clone();
this._cornersMerc = [
this._cornersWgs84[0].forwardMercatorEPS01(),
this._cornersWgs84[1].forwardMercatorEPS01(),
this._cornersWgs84[2].forwardMercatorEPS01(),
this._cornersWgs84[3].forwardMercatorEPS01()
];
this._extentMerc = new Extent(
this._extentWgs84.southWest.forwardMercatorEPS01(),
this._extentWgs84.northEast.forwardMercatorEPS01()
);
let tempArr = new Float32Array(2);
if (this._projType === 0) {
doubleToTwoFloats2(this._extentWgs84.southWest.lon, tempArr);
this._extentWgs84ParamsHigh[0] = tempArr[0];
this._extentWgs84ParamsLow[0] = tempArr[1];
doubleToTwoFloats2(this._extentWgs84.southWest.lat, tempArr);
this._extentWgs84ParamsHigh[1] = tempArr[0];
this._extentWgs84ParamsLow[1] = tempArr[1];
this._extentWgs84ParamsHigh[2] = 2.0 / this._extentWgs84.getWidth();
this._extentWgs84ParamsHigh[3] = 2.0 / this._extentWgs84.getHeight();
} else {
doubleToTwoFloats2(this._extentMerc.southWest.lon, tempArr);
this._extentMercParamsHigh[0] = tempArr[0];
this._extentMercParamsLow[0] = tempArr[1];
doubleToTwoFloats2(this._extentMerc.southWest.lat, tempArr);
this._extentMercParamsHigh[1] = tempArr[0];
this._extentMercParamsLow[1] = tempArr[1];
this._extentMercParamsHigh[2] = 2.0 / this._extentMerc.getWidth();
this._extentMercParamsHigh[3] = 2.0 / this._extentMerc.getHeight();
}
// creates material frame textures
if (this._planet) {
let p = this._planet,
h = p.renderer!.handler,
gl = h.gl!;
gl.deleteTexture(this._materialTexture as WebGLTexture);
this._materialTexture = h.createEmptyTexture_l(this._frameWidth, this._frameHeight);
let gridBufferArr = this._planet._geoImageCreator.createGridBuffer(this._cornersWgs84, this._projType === 1);
this._gridBufferHigh = gridBufferArr[0];
this._gridBufferLow = gridBufferArr[1];
this._refreshFrame = false;
}
}
/**
* @public
* @override
* @param {Material} material - GeoImage material.
*/
public override abortMaterialLoading(material: Material) {
this._creationProceeding = false;
material.isLoading = false;
material.isReady = false;
}
/**
* Clear layer material.
* @public
* @override
*/
public override clear() {
let p = this._planet;
if (p) {
let gl = p.renderer!.handler.gl;
this._creationProceeding && p._geoImageCreator.remove(this);
p._clearLayerMaterial(this);
if (gl) {
gl.deleteBuffer(this._gridBufferHigh as WebGLBuffer);
gl.deleteBuffer(this._gridBufferLow as WebGLBuffer);
gl.deleteTexture(this._sourceTexture as WebGLTexture);
this._materialTexture && !this._materialTexture.default && gl.deleteTexture(this._materialTexture);
}
}
this._sourceTexture = null;
this._materialTexture = null;
this._gridBufferHigh = null;
this._gridBufferLow = null;
this._refreshFrame = true;
this._sourceCreated = false;
this._ready = false;
this._creationProceeding = false;
}
/**
* Sets layer visibility.
* @public
* @override
* @param {boolean} visibility - GeoImage visibility.
*/
public override setVisibility(visibility: boolean) {
if (visibility !== this._visibility) {
super.setVisibility(visibility);
// remove from creator
if (this._planet && this._sourceReady) {
if (visibility) {
this._planet._geoImageCreator.add(this);
} else {
this._planet._geoImageCreator.remove(this);
}
}
}
}
/**
* @public
* @param {Material} material - GeoImage material.
*/
public override clearMaterial(material: Material) {
material.texture = null;
material.isLoading = false;
material.isReady = false;
}
/**
* @public
* @override
* @returns {Array<number>} -
*/
public override applyMaterial(material: Material): NumberArray4 {
let segment = material.segment;
if (this._ready) {
material.applyTexture(this._materialTexture);
} else {
material.texture = this._planet!.transparentTexture;
!this._creationProceeding && this.loadMaterial(material);
}
let v0s, v0t;
if (this._projType === 0) {
v0s = this._extentWgs84;
v0t = segment._extent;
} else {
v0s = this._extentMerc;
v0t = segment.getExtentMerc();
}
let sSize_x = v0s.northEast.lon - v0s.southWest.lon;
let sSize_y = v0s.northEast.lat - v0s.southWest.lat;
let dV0s_x = (v0t.southWest.lon - v0s.southWest.lon) / sSize_x;
let dV0s_y = (v0s.northEast.lat - v0t.northEast.lat) / sSize_y;
let dSize_x = (v0t.northEast.lon - v0t.southWest.lon) / sSize_x;
let dSize_y = (v0t.northEast.lat - v0t.southWest.lat) / sSize_y;
return [dV0s_x, dV0s_y, dSize_x, dSize_y];
}
/**
* Gets frame width size in pixels.
* @public
* @returns {Number} Frame width.
*/
public get getFrameWidth(): number {
return this._frameWidth;
}
/**
* Gets frame height size in pixels.
* @public
* @returns {Number} Frame height.
*/
public get getFrameHeight(): number {
return this._frameHeight;
}
/**
* Method depends on GeoImage instance
* @protected
*/
protected _createSourceTexture() {
//empty
}
public _renderingProjType1() {
let p = this._planet!,
h = p.renderer!.handler,
gl = h.gl!,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
this._createSourceTexture();
let f = creator._framebuffer!;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.programs.geoImageTransform.activate();
let sh = h.programs.geoImageTransform._program;
let sha = sh.attributes,
shu = sh.uniforms;
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture as WebGLTexture);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1i(shu.isFullExtent, this._isFullExtent);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer as WebGLBuffer);
gl.vertexAttribPointer(sha.texCoords, 2, gl.UNSIGNED_SHORT, true, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBufferHigh as WebGLBuffer);
gl.vertexAttribPointer(sha.cornersHigh, this._gridBufferHigh!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBufferLow as WebGLBuffer);
gl.vertexAttribPointer(sha.cornersLow, this._gridBufferLow!.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParamsHigh, this._extentMercParamsHigh);
gl.uniform4fv(shu.extentParamsLow, this._extentMercParamsLow);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture as WebGLTexture);
gl.uniform1i(shu.sourceTexture, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, creator._indexBuffer as WebGLBuffer);
gl.drawElements(gl.TRIANGLE_STRIP, creator._indexBuffer!.numItems, gl.UNSIGNED_INT, 0);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
protected _renderingProjType0() {
let p = this._planet!,
h = p.renderer!.handler,
gl = h.gl!,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
this._createSourceTexture();
let f = creator._framebuffer!;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.programs.geoImageTransform.activate();
let sh = h.programs.geoImageTransform._program;
let sha = sh.attributes,
shu = sh.uniforms;
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture as WebGLTexture);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer as WebGLBuffer);
gl.vertexAttribPointer(sha.texCoords, 2, gl.UNSIGNED_SHORT, true, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBufferHigh as WebGLBuffer);
gl.vertexAttribPointer(sha.cornersHigh, this._gridBufferHigh!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBufferLow as WebGLBuffer);
gl.vertexAttribPointer(sha.cornersLow, this._gridBufferLow!.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParamsHigh, this._extentWgs84ParamsHigh);
gl.uniform4fv(shu.extentParamsLow, this._extentWgs84ParamsLow);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture as WebGLTexture);
gl.uniform1i(shu.sourceTexture, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, creator._indexBuffer as WebGLBuffer);
gl.drawElements(gl.TRIANGLE_STRIP, creator._indexBuffer!.numItems, gl.UNSIGNED_INT, 0);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
}
export {BaseGeoImage};