import {Camera} from "../camera/Camera";
import {Control} from "../control/Control";
import {cons} from "../cons";
import {createRendererEvents, RendererEvents, RendererEventsHandler} from "./RendererEvents";
import {depth} from "../shaders/depth";
import {EntityCollection} from "../entity/EntityCollection";
import {Framebuffer, Multisample} from "../webgl/index";
import {FontAtlas} from "../utils/FontAtlas";
import {Handler, WebGLBufferExt} from "../webgl/Handler";
import {input} from "../input/input";
import {isEmpty} from "../utils/shared";
import {LabelWorker} from "../entity/LabelWorker";
import {randomi} from "../math";
import {RenderNode} from "../scene/RenderNode";
import {screenFrame} from "../shaders/screenFrame";
import {toneMapping} from "../shaders/toneMapping";
import {TextureAtlas} from "../utils/TextureAtlas";
import {Vec2} from "../math/Vec2";
import {NumberArray3, Vec3} from "../math/Vec3";
import {Vec4} from "../math/Vec4";
interface IRendererParams {
controls?: Control[];
msaa?: number;
autoActivate?: boolean;
fontsSrc?: string;
gamma?: number;
exposure?: number;
}
interface IPickingObject {
_pickingColor?: Vec3;
_pickingColorU?: Float32Array;
}
interface IFrameCallbackHandler {
id: number;
callback: Function;
sender: any;
}
const MSAA_DEFAULT = 0;
let __pickingCallbackCounter__ = 0;
let __depthCallbackCounter__ = 0;
let __distanceCallbackCounter__ = 0;
function clientWaitAsync(gl: WebGL2RenderingContext, sync: WebGLSync, flags: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
function check() {
const res = gl.clientWaitSync(sync, flags, 0);
if (res == gl.WAIT_FAILED) {
reject();
} else if (res == gl.TIMEOUT_EXPIRED) {
requestAnimationFrame(check);
} else {
resolve();
}
}
check();
});
}
/**
* Represents high level WebGL context interface that starts WebGL handler working in real time.
* @class
* @param {Handler} handler - WebGL handler context.
* @param {Object} [params] - Renderer parameters:
* @fires EventsHandler<RendererEventsType>#draw
* @fires EventsHandler<RendererEventsType>#resize
* @fires EventsHandler<RendererEventsType>#mousemove
* @fires EventsHandler<RendererEventsType>#mousestop
* @fires EventsHandler<RendererEventsType>#lclick
* @fires EventsHandler<RendererEventsType>#rclick
* @fires EventsHandler<RendererEventsType>#mclick
* @fires EventsHandler<RendererEventsType>#ldblclick
* @fires EventsHandler<RendererEventsType>#rdblclick
* @fires EventsHandler<RendererEventsType>#mdblclick
* @fires EventsHandler<RendererEventsType>#lup
* @fires EventsHandler<RendererEventsType>#rup
* @fires EventsHandler<RendererEventsType>#mup
* @fires EventsHandler<RendererEventsType>#ldown
* @fires EventsHandler<RendererEventsType>#rdown
* @fires EventsHandler<RendererEventsType>#mdown
* @fires EventsHandler<RendererEventsType>#lhold
* @fires EventsHandler<RendererEventsType>#rhold
* @fires EventsHandler<RendererEventsType>#mhold
* @fires EventsHandler<RendererEventsType>#mousewheel
* @fires EventsHandler<RendererEventsType>#touchstart
* @fires EventsHandler<RendererEventsType>#touchend
* @fires EventsHandler<RendererEventsType>#touchcancel
* @fires EventsHandler<RendererEventsType>#touchmove
* @fires EventsHandler<RendererEventsType>#doubletouch
* @fires EventsHandler<RendererEventsType>#touchleave
* @fires EventsHandler<RendererEventsType>#touchenter
*/
let __resizeTimeout: any;
export interface HTMLDivElementExt extends HTMLDivElement {
attributions?: HTMLElement;
}
class Renderer {
/**
* Div element with WebGL canvas. Assigned in Globe class.
* @public
* @type {HTMLElement | null}
*/
public div: HTMLDivElementExt | null;
/**
* WebGL handler context.
* @public
* @type {Handler}
*/
public handler: Handler;
public exposure: number;
public gamma: number;
public whitepoint: number;
public brightThreshold: number;
/**
* Render nodes drawing queue.
* @public
* @type {Array.<RenderNode>}
*/
public _renderNodesArr: RenderNode[];
/**
* Render nodes store for the comfortable access by the node name.
* @public
* @type {Object.<RenderNode>}
*/
public renderNodes: Record<string, RenderNode>;
/**
* Current active camera.
* @public
* @type {Camera}
*/
public activeCamera: Camera | null;
/**
* Renderer events. Represents interface for setting events like mousemove, draw, keypress etc.
* @public
* @type {RendererEvents}
*/
public events: RendererEventsHandler;
/**
* Controls array.
* @public
* @type {Object}
*/
public controls: Record<string, Control>;
/**
* Provides exchange between controls.
* @public
* @type {any}
*/
public controlsBag: any;
/**
* Hash table for drawing objects.
* @public
* @type {Map<string, any>}
*/
public colorObjects: Map<string, any>;
/**
* Color picking objects rendering queue.
* @type {Function[]}
*/
protected _pickingCallbacks: IFrameCallbackHandler[];
/**
* Picking objects(labels and billboards) framebuffer.
* @public
* @type {Framebuffer}
*/
public pickingFramebuffer: Framebuffer | null;
protected _tempPickingPix_: Uint8Array;
/**
* @public
* @type {Framebuffer}
*/
public distanceFramebuffer: Framebuffer | null;
/**
* @type {Function[]}
*/
protected _distanceCallbacks: IFrameCallbackHandler[];
protected _tempDistancePix_: Uint8Array;
/**
* Depth objects rendering queue.
* @type {Function[]}
*/
protected _depthCallbacks: IFrameCallbackHandler[];
public depthFramebuffer: Framebuffer | null;
protected _msaa: number;
protected _internalFormat: string;
protected _format: string;
protected _type: string;
public sceneFramebuffer: Framebuffer | Multisample | null;
protected blitFramebuffer: Framebuffer | null;
protected toneMappingFramebuffer: Framebuffer | null;
protected _initialized: boolean;
/**
* Texture atlas for the billboards images. One atlas per node.
* @public
* @type {TextureAtlas}
*/
public billboardsTextureAtlas: TextureAtlas;
/**
* Texture atlas for the billboards images. One atlas per node.
* @public
* @type {TextureAtlas}
*/
public geoObjectsTextureAtlas: TextureAtlas;
/**
* Texture font atlas for the font families and styles. One atlas per node.
* @public
* @type {FontAtlas}
*/
public fontAtlas: FontAtlas;
protected _entityCollections: EntityCollection[];
protected _currentOutput: string;
protected _fnScreenFrame: Function | null;
public labelWorker: LabelWorker;
public __useDistanceFramebuffer__: boolean;
public screenDepthFramebuffer: Framebuffer | null;
public screenFramePositionBuffer: WebGLBufferExt | null;
public screenTexture: Record<string, WebGLTexture>;
public outputTexture: WebGLTexture | null;
protected _skipDistanceFrame: boolean;
protected _distancePixelBuffer: WebGLBuffer | null;
protected _skipPickingFrame: boolean;
protected _pickingPixelBuffer: WebGLBuffer | null;
protected _readDistanceBuffer: () => void;
protected _readPickingBuffer: () => void;
constructor(handler: Handler, params: IRendererParams = {}) {
this.div = null;
this.handler = handler;
this.exposure = params.exposure || 3.01;
this.gamma = params.gamma || 0.47;
this.whitepoint = 1.0;
this.brightThreshold = 0.9;
this._renderNodesArr = [];
this.renderNodes = {};
this.activeCamera = null;
this.events = createRendererEvents(this);
this.controls = {};
if (params.controls) {
for (let i in params.controls) {
this.controls[params.controls[i].name] = params.controls[i];
}
}
this.controlsBag = {};
this.colorObjects = new Map<string, any>();
this._pickingCallbacks = [];
this.pickingFramebuffer = null;
this._tempPickingPix_ = new Uint8Array([]);
this.distanceFramebuffer = null;
this._distanceCallbacks = [];
this._tempDistancePix_ = new Uint8Array([]);
this._depthCallbacks = [];
this.depthFramebuffer = null;
let urlParams = new URLSearchParams(location.search);
let msaaParam = urlParams.get('og_msaa');
if (msaaParam) {
this._msaa = Number(urlParams.get('og_msaa'));
} else {
this._msaa = params.msaa != undefined ? params.msaa : MSAA_DEFAULT;
}
this._internalFormat = "RGBA16F";
this._format = "RGBA";
this._type = "FLOAT";
this.sceneFramebuffer = null;
this.blitFramebuffer = null;
this.toneMappingFramebuffer = null;
this._initialized = false;
/**
* Texture atlas for the billboards images. One atlas per node.
* @public
* @type {TextureAtlas}
*/
this.billboardsTextureAtlas = new TextureAtlas();
/**
* Texture atlas for the billboards images. One atlas per node.
* @public
* @type {TextureAtlas}
*/
this.geoObjectsTextureAtlas = new TextureAtlas();
/**
* Texture font atlas for the font families and styles. One atlas per node.
* @public
* @type {FontAtlas}
*/
this.fontAtlas = new FontAtlas(params.fontsSrc);
this._entityCollections = [];
this._currentOutput = "screen";
this._fnScreenFrame = null;
this.labelWorker = new LabelWorker(4);
this.__useDistanceFramebuffer__ = true;
this.screenDepthFramebuffer = null;
this.screenFramePositionBuffer = null;
this.screenTexture = {};
this.outputTexture = null;
this._skipDistanceFrame = false;
this._distancePixelBuffer = null;
this._skipPickingFrame = false;
this._pickingPixelBuffer = null;
this._readDistanceBuffer = this._readDistanceBuffer_webgl2;
this._readPickingBuffer = this._readPickingBuffer_webgl2;
if (params.autoActivate || isEmpty(params.autoActivate)) {
this.start();
}
}
public enableBlendOneSrcAlpha() {
let gl = this.handler.gl!;
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
}
public enableBlendDefault() {
let gl = this.handler.gl!;
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
}
/**
* Sets renderer events activity.
* @param {Boolean} activity - Events activity.
*/
public setEventsActivity(activity: boolean) {
this.events.active = activity;
}
public addDepthCallback(sender: any, callback: Function) {
let id = __depthCallbackCounter__++;
this._depthCallbacks.push({
id: id, callback: callback, sender: sender
});
return id;
}
public removeDepthCallback(id: number) {
for (let i = 0; i < this._depthCallbacks.length; i++) {
if (id === this._depthCallbacks[i].id) {
this._depthCallbacks.splice(i, 1);
break;
}
}
}
public addDistanceCallback(sender: any, callback: Function) {
let id = __distanceCallbackCounter__++;
this._distanceCallbacks.push({
id: id, callback: callback, sender: sender
});
return id;
}
public removeDistanceCallback(id: number) {
for (let i = 0; i < this._distanceCallbacks.length; i++) {
if (id === this._distanceCallbacks[i].id) {
this._distanceCallbacks.splice(i, 1);
break;
}
}
}
/**
* Adds picking rendering callback function.
* @param {object} sender - Callback context.
* @param {Function} callback - Rendering callback.
* @returns {Number} Handler id
*/
public addPickingCallback(sender: any, callback: Function) {
let id = __pickingCallbackCounter__++;
this._pickingCallbacks.push({
id: id, callback: callback, sender: sender
});
return id;
}
/**
* Removes picking rendering callback function.
* @param {Number} id - Handler id to remove.
*/
public removePickingCallback(id: number) {
for (let i = 0; i < this._pickingCallbacks.length; i++) {
if (id === this._pickingCallbacks[i].id) {
this._pickingCallbacks.splice(i, 1);
break;
}
}
}
public getPickingObject<T>(r: number, g: number, b: number): T {
return this.colorObjects.get(`${r}_${g}_${b}`);
}
public getPickingObjectArr<T>(arr: NumberArray3 | Uint8Array): T {
return this.colorObjects.get(`${arr[0]}_${arr[1]}_${arr[2]}`);
}
public getPickingObject3v<T>(vec: Vec3 | Vec4): T {
return this.colorObjects.get(`${vec.x}_${vec.y}_${vec.z}`);
}
/**
* Assign picking color to the object.
* @public
* @param {Object} obj - Object that presume to be picked.
*/
public assignPickingColor<T>(obj: T & IPickingObject) {
if (!obj._pickingColor || obj._pickingColor.isZero()) {
let r = 0, g = 0, b = 0;
let str = "0_0_0";
while (!(r || g || b) || this.colorObjects.has(str)) {
r = randomi(1, 255);
g = randomi(1, 255);
b = randomi(1, 255);
str = `${r}_${g}_${b}`;
}
if (!obj._pickingColor) {
obj._pickingColor = new Vec3(r, g, b);
} else {
obj._pickingColor.set(r, g, b);
}
obj._pickingColorU = new Float32Array([r / 255, g / 255, b / 255]);
this.colorObjects.set(str, obj);
}
}
/**
* Removes picking color from object.
* @public
* @param {Object} obj - Object to remove picking color.
*/
public clearPickingColor<T>(obj: T & IPickingObject) {
if (obj._pickingColor && !obj._pickingColor.isZero()) {
let c = obj._pickingColor;
if (!c.isZero()) {
this.colorObjects.delete(`${c.x}_${c.y}_${c.z}`);
c.x = c.y = c.z = 0;
}
}
}
/**
* Get the client width.
* @public
* @returns {number} -
*/
public getWidth(): number {
return this.handler.canvas!.clientWidth;
}
/**
* Get the client height.
* @public
* @returns {number} -
*/
public getHeight(): number {
return this.handler.canvas!.clientHeight;
}
/**
* Get center of the canvas
* @public
* @returns {Vec2} -
*/
public getCenter(): Vec2 {
let cnv = this.handler.canvas!;
return new Vec2(Math.round(cnv.width * 0.5), Math.round(cnv.height * 0.5));
}
/**
* Get center of the screen viewport
* @public
* @returns {Vec2} -
*/
public getClientCenter(): Vec2 {
let cnv = this.handler.canvas!;
return new Vec2(Math.round(cnv.clientWidth * 0.5), Math.round(cnv.clientHeight * 0.5));
}
/**
* Add the given control to the renderer.
* @param {Control} control - Control.
*/
public addControl(control: Control) {
control.addTo(this);
}
/**
* Add the given controls array to the planet node.
* @param {Array.<Control>} cArr - Control array.
*/
public addControls(cArr: Control[]) {
for (let i = 0; i < cArr.length; i++) {
cArr[i].addTo(this);
}
}
/**
* Remove control from the renderer.
* @param {Control} control - Control.
*/
public removeControl(control: Control) {
control.remove();
}
public isInitialized(): boolean {
return this._initialized;
}
/**
* Renderer initialization.
* @public
*/
public initialize() {
if (this._initialized) {
return;
} else {
this._initialized = true;
}
this.handler.initialize();
this.billboardsTextureAtlas.assignHandler(this.handler);
this.geoObjectsTextureAtlas.assignHandler(this.handler);
this.fontAtlas.assignHandler(this.handler);
this.handler.setFrameCallback(() => {
this.draw();
});
this.activeCamera = new Camera(this, {
eye: new Vec3(0, 0, 0), look: new Vec3(0, 0, -1), up: new Vec3(0, 1, 0)
});
this.events.initialize();
// Bind console key
this.events.on("charkeypress", input.KEY_APOSTROPHE, function () {
cons.setVisibility(!cons.getVisibility());
});
this.handler.addProgram(screenFrame());
this.pickingFramebuffer = new Framebuffer(this.handler, {
width: 640, height: 480
});
this.pickingFramebuffer.init();
this._tempPickingPix_ = new Uint8Array(this.pickingFramebuffer.width * this.pickingFramebuffer.height * 4);
this.distanceFramebuffer = new Framebuffer(this.handler, {
width: 320, height: 240
});
this.distanceFramebuffer.init();
this._tempDistancePix_ = new Uint8Array(this.distanceFramebuffer.width * this.distanceFramebuffer.height * 4);
//this._tempDistancePix_ = new Uint8Array(4);
this.depthFramebuffer = new Framebuffer(this.handler, {
size: 2,
internalFormat: ["RGBA", "DEPTH_COMPONENT24"],
format: ["RGBA", "DEPTH_COMPONENT"],
type: ["UNSIGNED_BYTE", "UNSIGNED_INT"],
attachment: ["COLOR_ATTACHMENT", "DEPTH_ATTACHMENT"],
useDepth: false
});
this.depthFramebuffer.init();
this.screenDepthFramebuffer = new Framebuffer(this.handler, {
useDepth: false
});
this.screenDepthFramebuffer.init();
if (this.handler.gl!.type === "webgl") {
this._readDistanceBuffer = this._readDistanceBuffer_webgl1;
this._readPickingBuffer = this._readPickingBuffer_webgl1;
this.sceneFramebuffer = new Framebuffer(this.handler);
this.sceneFramebuffer.init();
this._fnScreenFrame = this._screenFrameNoMSAA;
this.screenTexture = {
screen: this.sceneFramebuffer!.textures[0],
picking: this.pickingFramebuffer!.textures[0],
distance: this.distanceFramebuffer!.textures[0],
depth: this.screenDepthFramebuffer!.textures[0]
};
} else {
let _maxMSAA = this.getMaxMSAA(this._internalFormat);
if (this._msaa > _maxMSAA) {
this._msaa = _maxMSAA;
}
this.handler.addPrograms([toneMapping()]);
this.handler.addPrograms([depth()]);
this.sceneFramebuffer = new Multisample(this.handler, {
size: 1,
msaa: this._msaa,
internalFormat: this._internalFormat,
filter: "LINEAR"
});
this.sceneFramebuffer.init();
this.blitFramebuffer = new Framebuffer(this.handler, {
size: 1,
useDepth: false,
internalFormat: this._internalFormat,
format: this._format,
type: this._type,
filter: "NEAREST"
});
this.blitFramebuffer.init();
this.toneMappingFramebuffer = new Framebuffer(this.handler, {
useDepth: false
});
this.toneMappingFramebuffer.init();
this._fnScreenFrame = this._screenFrameMSAA;
this.screenTexture = {
screen: this.toneMappingFramebuffer!.textures[0],
picking: this.pickingFramebuffer!.textures[0],
distance: this.distanceFramebuffer!.textures[0],
depth: this.screenDepthFramebuffer!.textures[0],
frustum: this.depthFramebuffer!.textures[0]
};
this._initReadPixelsBuffers();
}
this.handler.ONCANVASRESIZE = () => {
this._resizeStart();
this.events.dispatch(this.events.resize, this.handler.canvas);
this._resizeEnd();
//clearTimeout(__resizeTimeout);
// __resizeTimeout = setTimeout(() => {
// this._resizeEnd();
// this.events.dispatch(this.events.resizeend, this.handler.canvas);
// }, 320);
this.events.dispatch(this.events.resizeend, this.handler.canvas);
};
this.screenFramePositionBuffer = this.handler.createArrayBuffer(new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), 2, 4);
this.outputTexture = this.screenTexture.screen;
this._initializeRenderNodes();
this._initializeControls();
}
_initReadPixelsBuffers() {
let gl = this.handler.gl!;
this._distancePixelBuffer = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._distancePixelBuffer);
gl.bufferData(gl.PIXEL_PACK_BUFFER, this.distanceFramebuffer!.width * this.distanceFramebuffer!.height * 4, gl.STREAM_READ);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
this._pickingPixelBuffer = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._pickingPixelBuffer);
gl.bufferData(gl.PIXEL_PACK_BUFFER, this.pickingFramebuffer!.width * this.pickingFramebuffer!.height * 4, gl.STREAM_READ);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
}
public _initializeControls() {
let temp = this.controls;
this.controls = {};
for (let i in temp) {
this.addControl(temp[i]);
}
}
public resize() {
this._resizeEnd();
}
public setCurrentScreen(screenName: string) {
this._currentOutput = screenName;
if (this.screenTexture[screenName]) {
this.outputTexture = this.screenTexture[screenName];
}
}
public _resizeStart() {
let c = this.handler.canvas!;
this.activeCamera!.setAspectRatio(c.width / c.height);
this.sceneFramebuffer!.setSize(c.width * 0.5, c.height * 0.5);
this.blitFramebuffer && this.blitFramebuffer.setSize(c.width * 0.5, c.height * 0.5, true);
}
public _resizeEnd() {
let c = this.handler.canvas!;
this.activeCamera!.setAspectRatio(c.width / c.height);
this.sceneFramebuffer!.setSize(c.width, c.height);
this.blitFramebuffer && this.blitFramebuffer.setSize(c.width, c.height, true);
this.toneMappingFramebuffer && this.toneMappingFramebuffer.setSize(c.width, c.height, true);
this.depthFramebuffer && this.depthFramebuffer.setSize(c.clientWidth, c.clientHeight, true);
this.screenDepthFramebuffer && this.screenDepthFramebuffer.setSize(c.clientWidth, c.clientHeight, true);
if (this.handler.gl!.type === "webgl") {
this.screenTexture.screen = (this.sceneFramebuffer as Framebuffer)!.textures[0];
this.screenTexture.picking = this.pickingFramebuffer!.textures[0];
this.screenTexture.distance = this.distanceFramebuffer!.textures[0];
this.screenTexture.depth = this.screenDepthFramebuffer!.textures[0];
this.screenTexture.frustum = this.depthFramebuffer!.textures[0];
} else {
this.screenTexture.screen = this.toneMappingFramebuffer!.textures[0];
this.screenTexture.picking = this.pickingFramebuffer!.textures[0];
this.screenTexture.distance = this.distanceFramebuffer!.textures[0];
this.screenTexture.depth = this.screenDepthFramebuffer!.textures[0];
this.screenTexture.frustum = this.depthFramebuffer!.textures[0];
}
this.setCurrentScreen(this._currentOutput);
}
public removeNode(renderNode: RenderNode) {
// TODO: replace from RenderNode to this method
renderNode.remove();
}
/**
* Adds render node to the renderer.
* @public
* @param {RenderNode} renderNode - Render node.
*/
public addNode(renderNode: RenderNode) {
if (!this.renderNodes[renderNode.name]) {
renderNode.assign(this);
this._renderNodesArr.unshift(renderNode);
this.renderNodes[renderNode.name] = renderNode;
} else {
cons.logWrn(`Node name ${renderNode.name} already exists.`);
}
}
protected _initializeRenderNodes() {
for (let i = 0; i < this._renderNodesArr.length; i++) {
this._renderNodesArr[i].initialize();
}
}
/**
* Adds render node to the renderer before specific node.
* @public
* @param {RenderNode} renderNode - Render node.
* @param {RenderNode} renderNodeBefore - Insert before the renderNodeBefore node.
*/
public addNodeBefore(renderNode: RenderNode, renderNodeBefore: RenderNode) {
if (!this.renderNodes[renderNode.name]) {
renderNode.assign(this);
this.renderNodes[renderNode.name] = renderNode;
for (let i = 0; i < this._renderNodesArr.length; i++) {
if (this._renderNodesArr[i].isEqual(renderNodeBefore)) {
this._renderNodesArr.splice(i, 0, renderNode);
break;
}
}
this._renderNodesArr.unshift(renderNode);
} else {
cons.logWrn(`Node name ${renderNode.name} already exists.`);
}
}
/**
* Adds render nodes array to the renderer.
* @public
* @param {Array.<RenderNode>} nodesArr - Render nodes array.
*/
public addNodes(nodesArr: RenderNode[]) {
for (let i = 0; i < nodesArr.length; i++) {
this.addNode(nodesArr[i]);
}
}
public getMaxMSAA(internalFormat: string) {
let gl = this.handler.gl!;
let samples = gl.getInternalformatParameter(gl.RENDERBUFFER, (gl as any)[internalFormat], gl.SAMPLES);
return samples[0];
}
public getMSAA(): number {
return this._msaa;
}
/**
* TODO: replace with cache friendly linked list by BillboardHandler, LabelHandler etc.
*/
public enqueueEntityCollectionsToDraw(ecArr: EntityCollection[]) {
this._entityCollections.push.apply(this._entityCollections, ecArr);
}
/**
* Draws opaque items entity collections.
* @protected
*/
protected _drawOpaqueEntityCollections() {
let ec = this._entityCollections;
if (ec.length) {
this.enableBlendDefault();
// pointClouds pass
let i = ec.length;
while (i--) {
ec[i]._fadingOpacity && ec[i].pointCloudHandler.draw();
}
}
}
/**
* Draws transparent items entity collections.
* @protected
*/
protected _drawTransparentEntityCollections() {
let ec = this._entityCollections;
if (ec.length) {
let gl = this.handler.gl!;
this.enableBlendDefault();
// GeoObjects
let i = ec.length;
while (i--) {
let eci = ec[i];
if (ec[i]._fadingOpacity) {
eci.events.dispatch(eci.events.draw, eci);
ec[i].geoObjectHandler.draw();
}
}
// billboards pass
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.billboardsTextureAtlas.texture!);
i = ec.length;
while (i--) {
let eci = ec[i];
eci._fadingOpacity && eci.billboardHandler.draw();
}
// labels pass
let fa = this.fontAtlas.atlasesArr;
for (i = 0; i < fa.length; i++) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, fa[i].texture!);
}
i = ec.length;
while (i--) {
ec[i]._fadingOpacity && ec[i].labelHandler.draw();
}
// rays
i = ec.length;
while (i--) {
ec[i]._fadingOpacity && ec[i].rayHandler.draw();
}
// polyline pass
i = ec.length;
while (i--) {
ec[i]._fadingOpacity && ec[i].polylineHandler.draw();
}
// Strip pass
i = ec.length;
while (i--) {
ec[i]._fadingOpacity && ec[i].stripHandler.draw();
}
}
}
protected _clearEntityCollectionQueue() {
this._entityCollections.length = 0;
this._entityCollections = [];
}
/**
* Draw nodes.
* @public
*/
public draw() {
this.activeCamera!.checkMoveEnd();
let e = this.events;
e.handleEvents();
let sceneFramebuffer = this.sceneFramebuffer!;
sceneFramebuffer.activate();
let h = this.handler,
gl = h.gl!;
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
this.enableBlendDefault();
e.dispatch(e.draw, this);
let frustums = this.activeCamera!.frustums;
let pointerEvent = e.pointerEvent();
let mouseHold = e.mouseState.leftButtonDown || e.mouseState.rightButtonDown;
// Rendering scene nodes and entityCollections
let rn = this._renderNodesArr;
let k = frustums.length;
while (k--) {
this.activeCamera!.setCurrentFrustum(k);
gl.clear(gl.DEPTH_BUFFER_BIT);
let i = rn.length;
while (i--) {
rn[i].preDrawNode();
}
this._drawOpaqueEntityCollections();
i = rn.length;
while (i--) {
this.enableBlendDefault();
rn[i].drawNode();
}
this._drawTransparentEntityCollections();
this._clearEntityCollectionQueue();
e.dispatch(e.drawtransparent, this);
if (pointerEvent && !mouseHold) {
this._drawPickingBuffer();
}
this.__useDistanceFramebuffer__ && this._drawDistanceBuffer();
}
sceneFramebuffer.deactivate();
this.blitFramebuffer && (sceneFramebuffer as Multisample).blitTo(this.blitFramebuffer, 0);
if (pointerEvent) {
if (h.isWebGl2()) {
// It works ONLY for 0 (closest) frustum
this._drawDepthBuffer();
}
this._readPickingBuffer();
}
this.__useDistanceFramebuffer__ && this._readDistanceBuffer();
// Tone mapping followed by rendering on the screen
this._fnScreenFrame!();
e.dispatch(e.postdraw, this);
e.mouseState.wheelDelta = 0;
e.mouseState.justStopped = false;
e.mouseState.moving = false;
e.touchState.moving = false;
}
protected _screenFrameMSAA() {
let h = this.handler;
let sh = h.programs.toneMapping,
p = sh._program,
gl = h.gl!;
gl.disable(gl.DEPTH_TEST);
gl.bindBuffer(gl.ARRAY_BUFFER, this.screenFramePositionBuffer as WebGLBuffer);
gl.vertexAttribPointer(p.attributes.corners, 2, gl.FLOAT, false, 0, 0);
this.toneMappingFramebuffer!.activate();
sh.activate();
// screen texture
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.blitFramebuffer!.textures[0]);
gl.uniform1i(p.uniforms.hdrBuffer, 0);
gl.uniform1f(p.uniforms.gamma, this.gamma);
gl.uniform1f(p.uniforms.exposure, this.exposure);
gl.uniform1f(p.uniforms.whitepoint, this.whitepoint);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
this.toneMappingFramebuffer!.deactivate();
// SCREEN PASS
sh = h.programs.screenFrame;
p = sh._program;
sh.activate();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.outputTexture);
gl.uniform1i(p.uniforms.texture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.enable(gl.DEPTH_TEST);
}
protected _screenFrameNoMSAA() {
let h = this.handler;
let sh = h.programs.screenFrame,
p = sh._program,
gl = h.gl!;
gl.disable(gl.DEPTH_TEST);
sh.activate();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.outputTexture);
gl.uniform1i(p.uniforms.texture, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.screenFramePositionBuffer as WebGLBuffer);
gl.vertexAttribPointer(p.attributes.corners, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.enable(gl.DEPTH_TEST);
}
/**
* Draw picking objects framebuffer.
* @private
*/
protected _drawPickingBuffer() {
this.pickingFramebuffer!.activate();
let h = this.handler;
let gl = h.gl!;
if (this.activeCamera!.isFirstPass) {
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);
}
let dp = this._pickingCallbacks;
for (let i = 0, len = dp.length; i < len; i++) {
//
// draw picking scenes, usually we don't need blending,
// but sometimes set it manually in the callbacks
//
gl.disable(gl.BLEND);
/**
* This callback renders picking frame.
*/
dp[i].callback.call(dp[i].sender);
gl.enable(gl.BLEND);
}
this.pickingFramebuffer!.deactivate();
}
/**
* Draw picking objects framebuffer.
* @protected
*/
protected _drawDistanceBuffer() {
this.distanceFramebuffer!.activate();
let h = this.handler;
let gl = h.gl!;
if (this.activeCamera!.isFirstPass) {
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);
}
gl.disable(gl.BLEND);
let dp = this._distanceCallbacks;
let i = dp.length;
while (i--) {
/**
* This callback renders distance frame.
*/
dp[i].callback.call(dp[i].sender);
}
gl.enable(gl.BLEND);
this.distanceFramebuffer!.deactivate();
}
protected _drawDepthBuffer() {
this.depthFramebuffer!.activate();
let h = this.handler;
let gl = h.gl!;
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
let dp = this._depthCallbacks;
let i = dp.length;
while (i--) {
/**
* This callback renders depth frame.
*/
dp[i].callback.call(dp[i].sender);
}
this.depthFramebuffer!.deactivate();
//
// PASS to depth visualization
this.screenDepthFramebuffer!.activate();
let sh = h.programs.depth, p = sh._program;
gl.bindBuffer(gl.ARRAY_BUFFER, this.screenFramePositionBuffer as WebGLBuffer);
gl.vertexAttribPointer(p.attributes.corners, 2, gl.FLOAT, false, 0, 0);
sh.activate();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.depthFramebuffer!.textures[1]);
gl.uniform1i(p.uniforms.depthTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
this.screenDepthFramebuffer!.deactivate();
}
protected _readPickingBuffer_webgl1 = () => {
this.pickingFramebuffer!.activate();
this.pickingFramebuffer!.readAllPixels(this._tempPickingPix_);
this.pickingFramebuffer!.deactivate();
}
protected _readPickingBuffer_webgl2 = () => {
const gl = this.handler.gl!;
const buf = this._pickingPixelBuffer;
if (!this._skipPickingFrame) {
this._skipPickingFrame = true;
let dest = this._tempPickingPix_;
let w = this.pickingFramebuffer!.width,
h = this.pickingFramebuffer!.height;
this.pickingFramebuffer!.activate();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
this.pickingFramebuffer!.deactivate();
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0)!;
gl.flush();
clientWaitAsync(gl, sync, 0).then(() => {
this._skipPickingFrame = false;
gl.deleteSync(sync);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, dest);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
});
}
}
protected _readDistanceBuffer_webgl1 = () => {
this.distanceFramebuffer!.activate();
this.distanceFramebuffer!.readAllPixels(this._tempDistancePix_);
this.distanceFramebuffer!.deactivate();
}
protected _readDistanceBuffer_webgl2 = () => {
const gl = this.handler.gl!;
const buf = this._distancePixelBuffer;
if (!this._skipDistanceFrame) {
this._skipDistanceFrame = true;
let dest = this._tempDistancePix_;
let w = this.distanceFramebuffer!.width,
h = this.distanceFramebuffer!.height;
this.distanceFramebuffer!.activate();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
this.distanceFramebuffer!.deactivate();
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0)!;
gl.flush();
clientWaitAsync(gl, sync, 0).then(() => {
this._skipDistanceFrame = false;
gl.deleteSync(sync);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, dest);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
});
}
}
public readPickingColor(x: number, y: number, outColor: NumberArray3 | Uint8Array) {
let w = this.pickingFramebuffer!.width;
let h = this.pickingFramebuffer!.height;
x = Math.round(x * w);
y = Math.round(y * h);
let ind = (y * w + x) * 4;
outColor[0] = this._tempPickingPix_[ind];
outColor[1] = this._tempPickingPix_[ind + 1];
outColor[2] = this._tempPickingPix_[ind + 2];
}
public readDistanceColor(x: number, y: number, outColor: NumberArray3 | Uint8Array) {
let w = this.distanceFramebuffer!.width;
let h = this.distanceFramebuffer!.height;
x = Math.round(x * w);
y = Math.round(y * h);
let ind = (y * w + x) * 4;
outColor[0] = this._tempDistancePix_[ind];
outColor[1] = this._tempDistancePix_[ind + 1];
outColor[2] = this._tempDistancePix_[ind + 2];
}
/**
* Function starts renderer
* @public
*/
public start() {
if (!this._initialized) {
this.initialize();
}
this.handler.start();
}
public destroy() {
for (let i in this.controls) {
this.controls[i].remove();
}
for (let i = 0; i < this._renderNodesArr.length; i++) {
this._renderNodesArr[i].remove();
}
this.div = null;
this._renderNodesArr = [];
this.renderNodes = {};
this.activeCamera = null;
this.controls = {};
this.controlsBag = {};
this.colorObjects.clear();
// @ts-ignore
this.colorObjects = null;
this._pickingCallbacks = [];
this.pickingFramebuffer = null;
//@ts-ignore
this._tempPickingPix_ = null;
this.distanceFramebuffer = null;
this._distanceCallbacks = [];
//@ts-ignore
this._tempDistancePix_ = null;
this._depthCallbacks = [];
this.depthFramebuffer = null;
this.sceneFramebuffer = null;
this.blitFramebuffer = null;
this.toneMappingFramebuffer = null;
// todo
//this.billboardsTextureAtlas.clear();
//this.geoObjectsTextureAtlas.clear()
//this.fontAtlas.clear();
this._entityCollections = [];
this.handler.ONCANVASRESIZE = null;
this.handler.destroy();
// @ts-ignore
this.handler = null;
this._initialized = false;
}
}
export {Renderer};