entity_geoObject_GeoObjectHandler.ts

import * as shaders from "../../shaders/geo_object/geo_object";
import { concatArrays, spliceArray } from "../../utils/shared";
import type { TypedArray } from "../../utils/shared";
import type { EntityCollection } from "../EntityCollection";
import { GeoObject } from "./GeoObject";
import { Vec3 } from "../../math/Vec3";
import { Vec4 } from "../../math/Vec4";
import { Quat } from "../../math/Quat";
import { Object3d } from "../../Object3d";
import { InstanceData } from "./InstanceData";
import type { Renderer } from "../../renderer/Renderer";
import type { Atmosphere } from "../../control/atmosphere/Atmosphere";
import type { Planet } from "../../scene/Planet";
import type { Scene } from "../../scene/Scene";
import type { ShaderProgram } from "../../webgl/ShaderProgram";
import { srgbToLinear } from "../../utils/colorSpace";

export const VERTEX_BUFFER = 0;
export const RTC_POSITION_BUFFER = 1;
export const RGBA_BUFFER = 2;
export const NORMALS_BUFFER = 3;
export const INDEX_BUFFER = 4;
export const QROT_BUFFER = 5;
export const SIZE_BUFFER = 6;
export const PICKINGCOLOR_BUFFER = 7;
export const VISIBLE_BUFFER = 8;
export const TEXCOORD_BUFFER = 9;
export const TRANSLATE_BUFFER = 10;
export const LOCALPOSITION_BUFFER = 11;

const OPAQUE_ALPHA_THRESHOLD = 0.999999;

function setParametersToArray(
    arr: number[] | TypedArray,
    index: number = 0,
    length: number = 0,
    itemSize: number = 1,
    ...params: number[]
): number[] | TypedArray {
    const currIndex = index * length;
    for (let i = currIndex, len = currIndex + length; i < len; i++) {
        arr[i] = params[i % itemSize];
    }
    return arr;
}

export class GeoObjectHandler {
    static __counter__ = 0;

    protected __id: number;

    /**
     * Picking rendering option.
     * @public
     * @type {boolean}
     */
    public pickingEnabled: boolean;

    protected _entityCollection: EntityCollection;

    public _scene: Scene | null;
    public _renderer: Renderer | null;

    protected _geoObjects: GeoObject[];

    protected _instanceDataMap: Map<string, InstanceData>;
    protected _instanceDataMapValues: InstanceData[];
    protected _dataTagUpdateQueue: InstanceData[];

    protected _relativeCenter: Vec3;

    protected _rtcEyePositionHigh: Float32Array;
    protected _rtcEyePositionLow: Float32Array;

    /**
     * @param {EntityCollection} entityCollection - Parent entity collection.
     */
    constructor(entityCollection: EntityCollection) {
        this.__id = GeoObjectHandler.__counter__++;

        this.pickingEnabled = true;

        this._entityCollection = entityCollection;

        this._scene = null;
        this._renderer = null;

        this._geoObjects = [];

        this._instanceDataMap = new Map<string, InstanceData>();
        this._instanceDataMapValues = [];

        this._dataTagUpdateQueue = [];

        this._relativeCenter = new Vec3();

        this._rtcEyePositionHigh = new Float32Array([0, 0, 0]);
        this._rtcEyePositionLow = new Float32Array([0, 0, 0]);
    }

    protected _isOpaqueAlpha(alpha: number): boolean {
        return alpha >= OPAQUE_ALPHA_THRESHOLD;
    }

    protected _markPerInstanceBuffersChanged(tagData: InstanceData) {
        tagData._changedBuffers[RTC_POSITION_BUFFER] = true;
        tagData._changedBuffers[RGBA_BUFFER] = true;
        tagData._changedBuffers[QROT_BUFFER] = true;
        tagData._changedBuffers[SIZE_BUFFER] = true;
        tagData._changedBuffers[PICKINGCOLOR_BUFFER] = true;
        tagData._changedBuffers[VISIBLE_BUFFER] = true;
        tagData._changedBuffers[TRANSLATE_BUFFER] = true;
        tagData._changedBuffers[LOCALPOSITION_BUFFER] = true;
    }

    protected _swapArrayItems(arr: number[] | TypedArray, itemSize: number, firstIndex: number, secondIndex: number) {
        if (firstIndex === secondIndex) {
            return;
        }

        const firstOffset = firstIndex * itemSize;
        const secondOffset = secondIndex * itemSize;

        for (let i = 0; i < itemSize; i++) {
            const tmp = arr[firstOffset + i];
            arr[firstOffset + i] = arr[secondOffset + i];
            arr[secondOffset + i] = tmp;
        }
    }

    protected _swapInstanceData(tagData: InstanceData, firstIndex: number, secondIndex: number) {
        if (firstIndex === secondIndex) {
            return;
        }

        this._swapArrayItems(tagData._visibleArr, 1, firstIndex, secondIndex);
        this._swapArrayItems(tagData._rtcPositionHighArr, 3, firstIndex, secondIndex);
        this._swapArrayItems(tagData._rtcPositionLowArr, 3, firstIndex, secondIndex);
        this._swapArrayItems(tagData._pickingColorArr, 3, firstIndex, secondIndex);
        this._swapArrayItems(tagData._qRotArr, 4, firstIndex, secondIndex);
        this._swapArrayItems(tagData._rgbaArr, 4, firstIndex, secondIndex);
        this._swapArrayItems(tagData._sizeArr, 3, firstIndex, secondIndex);
        this._swapArrayItems(tagData._translateArr, 3, firstIndex, secondIndex);
        this._swapArrayItems(tagData._localPositionArr, 3, firstIndex, secondIndex);

        const firstObject = tagData.geoObjects[firstIndex];
        const secondObject = tagData.geoObjects[secondIndex];
        tagData.geoObjects[firstIndex] = secondObject;
        tagData.geoObjects[secondIndex] = firstObject;
        firstObject._tagDataIndex = secondIndex;
        secondObject._tagDataIndex = firstIndex;
    }

    protected _insertInstanceByOpacity(tagData: InstanceData, instanceIndex: number, isOpaque: boolean) {
        if (!isOpaque) {
            return;
        }

        const targetIndex = tagData._opaqueInstanceCount;
        this._swapInstanceData(tagData, instanceIndex, targetIndex);
        tagData._opaqueInstanceCount++;
    }

    protected _updateInstanceOpacityState(tagData: InstanceData, instanceIndex: number, isOpaque: boolean): boolean {
        const wasOpaque = instanceIndex < tagData._opaqueInstanceCount;
        if (wasOpaque === isOpaque) {
            return false;
        }

        if (isOpaque) {
            const targetIndex = tagData._opaqueInstanceCount;
            this._swapInstanceData(tagData, instanceIndex, targetIndex);
            tagData._opaqueInstanceCount++;
        } else {
            tagData._opaqueInstanceCount--;
            const targetIndex = tagData._opaqueInstanceCount;
            this._swapInstanceData(tagData, instanceIndex, targetIndex);
        }

        return true;
    }

    public initProgram() {
        if (this._renderer && this._scene) {
            let programs = [
                shaders.geo_object_forward(),
                shaders.geo_object_deferred(),
                shaders.geo_object_woit(),
                shaders.geo_object_picking(),
                shaders.geo_object_depth()
            ];
            const atmosphereControl = (this._scene as Scene & { atmosphereControl?: Atmosphere }).atmosphereControl;
            if (atmosphereControl) {
                programs.push(shaders.geo_object_woit_atmos(atmosphereControl.parameters));
            }
            this._renderer.addPrograms(programs);
        }
    }

    public bindScene(scene: Scene) {
        this._scene = scene;

        this._renderer = scene.renderer;

        this.initProgram();

        //
        // in case of lazy initialization loading data here
        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            this._instanceDataMapValues[i].loadColorTexture();
            this._instanceDataMapValues[i].loadNormalTexture();
            this._instanceDataMapValues[i].loadMetallicRoughnessTexture();
        }

        for (let i = 0; i < this._geoObjects.length; i++) {
            this._geoObjects[i].updateRotation();
        }

        this.update();
    }

    public setColorTextureTag(src: string | HTMLImageElement, tag: string) {
        const tagData = this._instanceDataMap.get(tag);
        if (tagData) {
            if (typeof src === "string") {
                tagData._colorTextureSrc = src;
                tagData._colorTextureImage = null;
            }
            if (src instanceof HTMLImageElement) {
                tagData._colorTextureSrc = null;
                tagData._colorTextureImage = src;
            }
            this._instanceDataMap.set(tag, tagData);
            tagData.loadColorTexture();
        }
    }

    public setNormalTextureTag(src: string | HTMLImageElement, tag: string) {
        const tagData = this._instanceDataMap.get(tag);
        if (tagData) {
            if (typeof src === "string") {
                tagData._normalTextureSrc = src;
                tagData._normalTextureImage = null;
            }
            if (src instanceof HTMLImageElement) {
                tagData._normalTextureSrc = null;
                tagData._normalTextureImage = src;
            }
            this._instanceDataMap.set(tag, tagData);
            tagData.loadNormalTexture();
        }
    }

    public setMetallicRoughnessTextureTag(src: string | HTMLImageElement, tag: string) {
        const tagData = this._instanceDataMap.get(tag);
        if (tagData) {
            if (typeof src === "string") {
                tagData._metallicRoughnessTextureSrc = src;
                tagData._metallicRoughnessTextureImage = null;
            }
            if (src instanceof HTMLImageElement) {
                tagData._metallicRoughnessTextureSrc = null;
                tagData._metallicRoughnessTextureImage = src;
            }
            this._instanceDataMap.set(tag, tagData);
            tagData.loadMetallicRoughnessTexture();
        }
    }

    public setObjectSrc(src: string, tag: string) {
        const tagData = this._instanceDataMap.get(tag);
        if (src) {
            if (tagData && tagData._objectSrc !== src) {
                tagData._objectSrc = src;

                Object3d.loadObj(src).then((object3d) => {
                    this._updateInstanceData(object3d[0], tag);
                });
            }
        }
    }

    public _updateInstanceData(object: Object3d, tag: string) {
        const tagData = this._instanceDataMap.get(tag);
        if (tagData) {
            if (object.vertices.length !== tagData._vertexArr.length) {
                tagData._vertexArr = object.vertices;
                tagData._changedBuffers[VERTEX_BUFFER] = true;
            }
            if (object.normals.length !== tagData._normalsArr.length) {
                tagData._normalsArr = object.normals;
                tagData._changedBuffers[NORMALS_BUFFER] = true;
            }
            if (object.indices.length !== tagData._indicesArr.length) {
                tagData._indicesArr = object.indices;
                tagData._changedBuffers[INDEX_BUFFER] = true;
            }
            if (object.texCoords.length !== tagData._texCoordArr.length) {
                tagData._texCoordArr = object.texCoords;
                tagData._changedBuffers[TEXCOORD_BUFFER] = true;
            }

            tagData._colorTextureSrc = object.colorTextureSrc;
            tagData._normalTextureSrc = object.normalTextureSrc;
            tagData._metallicRoughnessTextureSrc = object.metallicRoughnessTextureSrc;
            tagData._colorTextureImage = object.colorTextureImage;
            tagData._normalTextureImage = object.normalTextureImage;
            tagData._metallicRoughnessTextureImage = object.metallicRoughnessTextureImage;

            tagData.setMaterialProperties(object.ambientOcclusion, object.roughness, object.metallic);

            tagData.loadColorTexture();
            tagData.loadNormalTexture();
            tagData.loadMetallicRoughnessTexture();

            this._updateTag(tagData);
            this._instanceDataMapValues = Array.from(this._instanceDataMap.values());
        }
    }

    protected _addGeoObjectToArray(geoObject: GeoObject) {
        const tag = geoObject.tag;

        let tagData = this._instanceDataMap.get(tag);

        if (!tagData) {
            tagData = new InstanceData(this);
            this._instanceDataMap.set(tag, tagData);
            this._instanceDataMapValues = Array.from(this._instanceDataMap.values());

            //
            // Setting instanced data
            tagData._vertexArr = geoObject.vertices;
            tagData._normalsArr = geoObject.normals;
            tagData._indicesArr = geoObject.indices;
            tagData._texCoordArr = geoObject.texCoords;

            tagData._colorTextureSrc = geoObject.object3d.colorTextureSrc;
            tagData._normalTextureSrc = geoObject.object3d.normalTextureSrc;
            tagData._metallicRoughnessTextureSrc = geoObject.object3d.metallicRoughnessTextureSrc;
            tagData._colorTextureImage = geoObject.object3d.colorTextureImage;
            tagData._normalTextureImage = geoObject.object3d.normalTextureImage;
            tagData._metallicRoughnessTextureImage = geoObject.object3d.metallicRoughnessTextureImage;

            tagData.setMaterialProperties(
                geoObject.object3d.ambientOcclusion,
                geoObject.object3d.roughness,
                geoObject.object3d.metallic
            );

            tagData.loadColorTexture();
            tagData.loadNormalTexture();
            tagData.loadMetallicRoughnessTexture();
        }

        geoObject._tagDataIndex = tagData.numInstances++;
        geoObject._tagData = tagData;
        tagData.geoObjects.push(geoObject);

        let itemSize = 3;

        tagData._visibleArr = concatArrays(
            tagData._visibleArr,
            setParametersToArray([], 0, 1, 1, geoObject.getVisibility() ? 1 : 0)
        );

        //
        // Global coordinates
        this.getRTCPosition(geoObject.getPosition(), geoObject._rtcPositionHigh, geoObject._rtcPositionLow);

        let x = geoObject._rtcPositionHigh.x,
            y = geoObject._rtcPositionHigh.y,
            z = geoObject._rtcPositionHigh.z,
            w;

        tagData._rtcPositionHighArr = concatArrays(
            tagData._rtcPositionHighArr,
            setParametersToArray([], 0, itemSize, itemSize, x, y, z)
        );

        x = geoObject._rtcPositionLow.x;
        y = geoObject._rtcPositionLow.y;
        z = geoObject._rtcPositionLow.z;
        tagData._rtcPositionLowArr = concatArrays(
            tagData._rtcPositionLowArr,
            setParametersToArray([], 0, itemSize, itemSize, x, y, z)
        );

        x = geoObject._entity!._pickingColor.x / 255;
        y = geoObject._entity!._pickingColor.y / 255;
        z = geoObject._entity!._pickingColor.z / 255;
        tagData._pickingColorArr = concatArrays(
            tagData._pickingColorArr,
            setParametersToArray([], 0, itemSize, itemSize, x, y, z)
        );

        itemSize = 4;

        x = geoObject._qRot.x;
        y = geoObject._qRot.y;
        z = geoObject._qRot.z;
        w = geoObject._qRot.w;
        tagData._qRotArr = concatArrays(tagData._qRotArr, setParametersToArray([], 0, itemSize, itemSize, x, y, z, w));

        x = srgbToLinear(geoObject._color.x);
        y = srgbToLinear(geoObject._color.y);
        z = srgbToLinear(geoObject._color.z);
        w = geoObject._color.w;
        tagData._rgbaArr = concatArrays(tagData._rgbaArr, setParametersToArray([], 0, itemSize, itemSize, x, y, z, w));

        itemSize = 3;
        let scale = geoObject.getScale();
        x = scale.x;
        y = scale.y;
        z = scale.z;
        tagData._sizeArr = concatArrays(tagData._sizeArr, setParametersToArray([], 0, itemSize, itemSize, x, y, z));

        let translate = geoObject.getTranslate();
        x = translate.x;
        y = translate.y;
        z = translate.z;
        tagData._translateArr = concatArrays(
            tagData._translateArr,
            setParametersToArray([], 0, itemSize, itemSize, x, y, z)
        );

        let localPosition = geoObject.getLocalPosition();
        x = localPosition.x;
        y = localPosition.y;
        z = localPosition.z;
        tagData._localPositionArr = concatArrays(
            tagData._localPositionArr,
            setParametersToArray([], 0, itemSize, itemSize, x, y, z)
        );

        this._insertInstanceByOpacity(tagData, geoObject._tagDataIndex, this._isOpaqueAlpha(geoObject._color.w));
    }

    //
    // Could be in VAO
    //
    protected _bindCommon(p: ShaderProgram) {
        let r = this._renderer!,
            u = p.uniforms,
            gl = r.handler.gl!,
            ec = this._entityCollection;

        gl.uniform4f(
            u.uScaleByDistance,
            ec.scaleByDistance[0],
            ec.scaleByDistance[1],
            ec.scaleByDistance[2],
            r.activeCamera.isOrthographic ? r.activeCamera.focusDistance : 0.0
        );
        gl.uniform1f(u.shadeMode, ec._shadeMode);

        gl.uniform3fv(u.rtcEyePositionHigh, this._rtcEyePositionHigh);
        gl.uniform3fv(u.rtcEyePositionLow, this._rtcEyePositionLow);

        gl.uniform3fv(u.eyePositionHigh, r.activeCamera.eyeHigh);
        gl.uniform3fv(u.eyePositionLow, r.activeCamera.eyeLow);

        gl.uniformMatrix4fv(u.projectionMatrix, false, r.activeCamera.getProjectionMatrix());
        gl.uniformMatrix4fv(u.viewMatrix, false, r.activeCamera.getViewMatrix());
        gl.uniformMatrix3fv(u.normalMatrix, false, r.activeCamera.getNormalMatrix());
    }

    protected _bindForwardParams(p: ShaderProgram) {
        let r = this._renderer!,
            u = p.uniforms,
            gl = r.handler.gl!;

        //
        // Global sun position
        gl.uniform3fv(u.lightPosition, r._lightPosition);
        gl.uniform3fv(u.lightAmbient, r._lightAmbient);
        gl.uniform3fv(u.lightDiffuse, r._lightDiffuse);
        gl.uniform4fv(u.lightSpecular, r._lightSpecular);
    }

    protected _bindAtmosphereParams(p: ShaderProgram) {
        let r = this._renderer!,
            gl = r.handler.gl!,
            u = p.uniforms,
            atmosphere = r.controls.Atmosphere as Atmosphere,
            planet = this._scene as Planet;

        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, atmosphere._transmittanceBuffer!.textures[0]);
        gl.uniform1i(u.transmittanceTexture, 1);

        gl.activeTexture(gl.TEXTURE2);
        gl.bindTexture(gl.TEXTURE_2D, atmosphere._scatteringBuffer!.textures[0]);
        gl.uniform1i(u.scatteringTexture, 2);
        gl.uniform2fv(u.atmosFadeDist, planet.atmosphereFadeDist);
        gl.uniform2fv(u.atmosMaxMinOpacity, planet.atmosphereMaxMinOpacity);

        gl.activeTexture(gl.TEXTURE0);
    }

    public _displayOpaquePASS() {
        let r = this._renderer!,
            sh = r.handler.programs.geo_object_deferred,
            p = sh,
            gl = r.handler.gl!,
            disableCull = this._entityCollection.disableCullFace;

        sh.activate();

        this._bindCommon(p);

        if (disableCull) gl.disable(gl.CULL_FACE);

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            this._instanceDataMapValues[i].drawOpaque(p);
        }

        if (disableCull) gl.enable(gl.CULL_FACE);
    }

    public _displayTransparentPASS() {
        let r = this._renderer!,
            rn = this._scene,
            //@ts-ignore
            useAtmos = rn.atmosphereEnabled,
            sh = useAtmos ? r.handler.programs.geo_object_woit_atmos : r.handler.programs.geo_object_woit,
            p = sh,
            gl = r.handler.gl!,
            disableCull = this._entityCollection.disableCullFace;

        sh.activate();

        gl.uniform1f(p.uniforms.useReverseDepth, r.activeCamera.reverseDepthActive ? 1.0 : 0.0);

        if (disableCull) gl.disable(gl.CULL_FACE);

        this._bindCommon(p);
        this._bindForwardParams(p);
        if (useAtmos) {
            this._bindAtmosphereParams(p);
        }

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            this._instanceDataMapValues[i].drawTransparent(p);
        }

        if (disableCull) gl.enable(gl.CULL_FACE);
    }

    public _displayTransparentForwardPASS() {
        let r = this._renderer!,
            sh = r.handler.programs.geo_object_forward,
            p = sh;

        sh.activate();

        this._bindCommon(p);
        this._bindForwardParams(p);

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            this._instanceDataMapValues[i].drawTransparent(p);
        }
    }

    public _displayForwardPASS() {
        let r = this._renderer!,
            sh = r.handler.programs.geo_object_forward,
            p = sh;

        sh.activate();

        this._bindCommon(p);
        this._bindForwardParams(p);

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            this._instanceDataMapValues[i].drawForwardAll(p);
        }
    }

    protected _depthPASS() {
        let r = this._renderer!,
            sh = r.handler.programs.geo_object_depth,
            p = sh,
            u = p.uniforms,
            a = p.attributes,
            gl = r.handler.gl!,
            ec = this._entityCollection;

        let cam = r.activeCamera!;

        sh.activate();

        gl.uniform4f(
            u.uScaleByDistance,
            ec.scaleByDistance[0],
            ec.scaleByDistance[1],
            ec.scaleByDistance[2],
            cam.isOrthographic ? cam.focusDistance : 0.0
        );

        gl.uniform3fv(u.rtcEyePositionHigh, this._rtcEyePositionHigh);
        gl.uniform3fv(u.rtcEyePositionLow, this._rtcEyePositionLow);

        gl.uniformMatrix4fv(u.projectionMatrix, false, r.activeCamera!.getProjectionMatrix());
        gl.uniformMatrix4fv(u.viewMatrix, false, r.activeCamera!.getViewMatrix());

        gl.uniform1f(u.frustumPickingColor, cam.frustumColorIndex);

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            let tagData = this._instanceDataMapValues[i];

            //
            // Instance individual data
            //
            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._qRotBuffer!);
            gl.vertexAttribPointer(a.qRot, tagData._qRotBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._sizeBuffer!);
            gl.vertexAttribPointer(a.aScale, tagData._sizeBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._translateBuffer!);
            gl.vertexAttribPointer(a.aTranslate, tagData._translateBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._localPositionBuffer!);
            gl.vertexAttribPointer(a.aLocalPosition, tagData._localPositionBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._rtcPositionHighBuffer!);
            gl.vertexAttribPointer(a.aRTCPositionHigh, tagData._rtcPositionHighBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._rtcPositionLowBuffer!);
            gl.vertexAttribPointer(a.aRTCPositionLow, tagData._rtcPositionLowBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._visibleBuffer!);
            gl.vertexAttribPointer(a.aDispose, tagData._visibleBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            //
            // Instance common data(could be in VAO)
            //
            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._vertexBuffer!);
            gl.vertexAttribPointer(a.aVertexPosition, tagData._vertexBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, tagData._indicesBuffer!);
            p.drawElementsInstanced!(
                gl.TRIANGLES,
                tagData._indicesBuffer!.numItems,
                gl.UNSIGNED_INT,
                0,
                tagData.numInstances
            );
        }
    }

    public drawDepth() {
        if (this._geoObjects.length && this.pickingEnabled) {
            this._depthPASS();
        }
    }

    public drawPicking() {
        if (this._geoObjects.length && this.pickingEnabled) {
            this._pickingPASS();
        }
    }

    protected _pickingPASS() {
        let r = this._renderer!,
            sh = r.handler.programs.geo_object_picking,
            p = sh,
            u = p.uniforms,
            a = p.attributes,
            gl = r.handler.gl!,
            ec = this._entityCollection;

        sh.activate();

        gl.uniform4f(
            u.uScaleByDistance,
            ec.scaleByDistance[0],
            ec.scaleByDistance[1],
            ec.scaleByDistance[2],
            r.activeCamera.isOrthographic ? r.activeCamera.focusDistance : 0.0
        );
        gl.uniform3fv(u.pickingScale, ec.pickingScale);

        gl.uniform3fv(u.rtcEyePositionHigh, this._rtcEyePositionHigh);
        gl.uniform3fv(u.rtcEyePositionLow, this._rtcEyePositionLow);

        gl.uniformMatrix4fv(u.projectionMatrix, false, r.activeCamera.getProjectionMatrix());
        gl.uniformMatrix4fv(u.viewMatrix, false, r.activeCamera.getViewMatrix());

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            let tagData = this._instanceDataMapValues[i];

            //
            // Instance individual data
            //
            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._qRotBuffer!);
            gl.vertexAttribPointer(a.qRot, tagData._qRotBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._sizeBuffer!);
            gl.vertexAttribPointer(a.aScale, tagData._sizeBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._translateBuffer!);
            gl.vertexAttribPointer(a.aTranslate, tagData._translateBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._localPositionBuffer!);
            gl.vertexAttribPointer(a.aLocalPosition, tagData._localPositionBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._pickingColorBuffer!);
            gl.vertexAttribPointer(a.aPickingColor, tagData._pickingColorBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._rtcPositionHighBuffer!);
            gl.vertexAttribPointer(a.aRTCPositionHigh, tagData._rtcPositionHighBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._rtcPositionLowBuffer!);
            gl.vertexAttribPointer(a.aRTCPositionLow, tagData._rtcPositionLowBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._visibleBuffer!);
            gl.vertexAttribPointer(a.aDispose, tagData._visibleBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            //
            // Instance common data(could be in VAO)
            //
            gl.bindBuffer(gl.ARRAY_BUFFER, tagData._vertexBuffer!);
            gl.vertexAttribPointer(a.aVertexPosition, tagData._vertexBuffer!.itemSize, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, tagData._indicesBuffer!);
            p.drawElementsInstanced!(
                gl.TRIANGLES,
                tagData._indicesBuffer!.numItems,
                gl.UNSIGNED_INT,
                0,
                tagData.numInstances
            );
        }
    }

    public setQRotArr(tagData: InstanceData, tagDataIndex: number, qRot: Quat) {
        setParametersToArray(tagData._qRotArr, tagDataIndex, 4, 4, qRot.x, qRot.y, qRot.z, qRot.w);
        tagData._changedBuffers[QROT_BUFFER] = true;
        this._updateTag(tagData);
    }

    public setVisibility(tagData: InstanceData, tagDataIndex: number, visibility: boolean) {
        setParametersToArray(tagData._visibleArr, tagDataIndex, 1, 1, visibility ? 1 : 0);
        tagData._changedBuffers[VISIBLE_BUFFER] = true;
        this._updateTag(tagData);
    }

    public setRTCPositionArr(tagData: InstanceData, tagDataIndex: number, rtcPositionHigh: Vec3, rtcPositionLow: Vec3) {
        setParametersToArray(
            tagData._rtcPositionHighArr,
            tagDataIndex,
            3,
            3,
            rtcPositionHigh.x,
            rtcPositionHigh.y,
            rtcPositionHigh.z
        );
        setParametersToArray(
            tagData._rtcPositionLowArr,
            tagDataIndex,
            3,
            3,
            rtcPositionLow.x,
            rtcPositionLow.y,
            rtcPositionLow.z
        );
        tagData._changedBuffers[RTC_POSITION_BUFFER] = true;
        this._updateTag(tagData);
    }

    public setRgbaArr(tagData: InstanceData, tagDataIndex: number, rgba: Vec4) {
        setParametersToArray(
            tagData._rgbaArr,
            tagDataIndex,
            4,
            4,
            srgbToLinear(rgba.x),
            srgbToLinear(rgba.y),
            srgbToLinear(rgba.z),
            rgba.w
        );
        const opacityChanged = this._updateInstanceOpacityState(tagData, tagDataIndex, this._isOpaqueAlpha(rgba.w));
        if (opacityChanged) {
            this._markPerInstanceBuffersChanged(tagData);
        } else {
            tagData._changedBuffers[RGBA_BUFFER] = true;
        }
        this._updateTag(tagData);
    }

    public setPickingColorArr(tagData: InstanceData, tagDataIndex: number, color: Vec3) {
        setParametersToArray(tagData._pickingColorArr, tagDataIndex, 3, 3, color.x / 255, color.y / 255, color.z / 255);
        tagData._changedBuffers[PICKINGCOLOR_BUFFER] = true;
        this._updateTag(tagData);
    }

    // setTexCoordArr(tagData, tagDataIndex, tcoordArr) {
    //     setParametersToArray(tagData._texCoordArr, tagDataIndex, 2, 2, ...tcoordArr);
    //     tagData._changedBuffers[TEXCOORD_BUFFER] = true;
    //     this._updateTag(tagData);
    // }

    public setScaleArr(tagData: InstanceData, tagDataIndex: number, scale: Vec3) {
        setParametersToArray(tagData._sizeArr, tagDataIndex, 3, 3, scale.x, scale.y, scale.z);
        tagData._changedBuffers[SIZE_BUFFER] = true;
        this._updateTag(tagData);
    }

    public setTranslateArr(tagData: InstanceData, tagDataIndex: number, translate: Vec3) {
        setParametersToArray(tagData._translateArr, tagDataIndex, 3, 3, translate.x, translate.y, translate.z);
        tagData._changedBuffers[TRANSLATE_BUFFER] = true;
        this._updateTag(tagData);
    }

    public setLocalPositionArr(tagData: InstanceData, tagDataIndex: number, localPosition: Vec3) {
        setParametersToArray(
            tagData._localPositionArr,
            tagDataIndex,
            3,
            3,
            localPosition.x,
            localPosition.y,
            localPosition.z
        );
        tagData._changedBuffers[LOCALPOSITION_BUFFER] = true;
        this._updateTag(tagData);
    }

    protected _updateTag(dataTag: InstanceData) {
        if (dataTag.isFree) {
            dataTag.isFree = false;
            this._dataTagUpdateQueue.push(dataTag);
        }
    }

    public update() {
        for (let i = 0, len = this._dataTagUpdateQueue.length; i < len; i++) {
            this._dataTagUpdateQueue[i].update();
        }
        this._dataTagUpdateQueue = [];
    }

    public _removeAll() {
        let i = this._geoObjects.length;
        while (i--) {
            const gi = this._geoObjects[i];

            gi._tagDataIndex = -1;
            gi._tagData = null;

            gi._handlerIndex = -1;
            gi._handler = null;
        }
        this._geoObjects.length = 0;
        this._geoObjects = [];

        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            this._instanceDataMapValues[i].clear();
        }

        this._instanceDataMap.clear();
        this._instanceDataMapValues = [];
    }

    public clear() {
        this._removeAll();
    }

    public getRTCPosition(pos: Vec3, rtcPositionHigh: Vec3, rtcPositionLow: Vec3) {
        let rtcPosition = pos.sub(this._relativeCenter);
        Vec3.doubleToTwoFloats(rtcPosition, rtcPositionHigh, rtcPositionLow);
    }

    public setRelativeCenter(c: Vec3) {
        this._relativeCenter.copy(c);
        for (let i = 0; i < this._instanceDataMapValues.length; i++) {
            let instanceData = this._instanceDataMapValues[i];
            let geoObjects = instanceData.geoObjects;
            for (let j = 0; j < geoObjects.length; j++) {
                geoObjects[j].updateRTCPosition();
            }
        }
    }

    protected _updateRTCEyePosition() {
        let r = this._renderer!;
        if (r.activeCamera.isFarthestFrustumActive) {
            let rtcEyePosition = r.activeCamera.eye.sub(this._relativeCenter);
            Vec3.doubleToTwoFloat32Array(rtcEyePosition, this._rtcEyePositionHigh, this._rtcEyePositionLow);
        }
    }

    public drawForward() {
        if (this._geoObjects.length) {
            this._updateRTCEyePosition();
            this.update();
            this._displayForwardPASS();
        }
    }

    public drawOpaque() {
        if (this._geoObjects.length) {
            this._updateRTCEyePosition();
            this.update();
            this._displayOpaquePASS();
        }
    }

    public drawTransparent() {
        if (this._geoObjects.length) {
            this._displayTransparentPASS();
        }
    }

    public drawTransparentForward() {
        if (this._geoObjects.length) {
            this._displayTransparentForwardPASS();
        }
    }

    public add(geoObject: GeoObject) {
        if (geoObject._handlerIndex === -1) {
            geoObject._handler = this;
            geoObject._handlerIndex = this._geoObjects.length;

            this._geoObjects.push(geoObject);
            this._addGeoObjectToArray(geoObject);

            geoObject.updateRotation();

            geoObject._tagData!.refresh();

            this._updateTag(geoObject._tagData!);
            geoObject.setObjectSrc(geoObject._objectSrc!);
        }
    }

    public remove(geoObject: GeoObject) {
        if (geoObject._handler && this.__id == geoObject._handler.__id) {
            this._removeGeoObject(geoObject);
        }
    }

    public _clearDataTagQueue() {
        this._dataTagUpdateQueue = [];
    }

    public _removeGeoObject(geoObject: GeoObject) {
        let tagData = geoObject._tagData!;
        let tag = geoObject.tag;

        this._geoObjects.splice(geoObject._handlerIndex, 1);
        for (let i = geoObject._handlerIndex, len = this._geoObjects.length; i < len; i++) {
            let gi = this._geoObjects[i];
            gi._handlerIndex = gi._handlerIndex - 1;
        }

        let removeIndex = geoObject._tagDataIndex;

        if (removeIndex < tagData._opaqueInstanceCount) {
            tagData._opaqueInstanceCount--;
            this._swapInstanceData(tagData, removeIndex, tagData._opaqueInstanceCount);
            removeIndex = tagData._opaqueInstanceCount;
        }

        const lastIndex = tagData.numInstances - 1;
        this._swapInstanceData(tagData, removeIndex, lastIndex);
        tagData.geoObjects.pop();

        tagData._rgbaArr = spliceArray(tagData._rgbaArr, lastIndex * 4, 4);
        tagData._rtcPositionHighArr = spliceArray(tagData._rtcPositionHighArr, lastIndex * 3, 3);
        tagData._rtcPositionLowArr = spliceArray(tagData._rtcPositionLowArr, lastIndex * 3, 3);
        tagData._qRotArr = spliceArray(tagData._qRotArr, lastIndex * 4, 4);
        tagData._pickingColorArr = spliceArray(tagData._pickingColorArr, lastIndex * 3, 3);
        tagData._sizeArr = spliceArray(tagData._sizeArr, lastIndex * 3, 3);
        tagData._translateArr = spliceArray(tagData._translateArr, lastIndex * 3, 3);
        tagData._localPositionArr = spliceArray(tagData._localPositionArr, lastIndex * 3, 3);
        tagData._visibleArr = spliceArray(tagData._visibleArr, lastIndex, 1);
        tagData.numInstances--;

        geoObject._handlerIndex = -1;
        geoObject._handler = null;

        geoObject._tagDataIndex = -1;
        geoObject._tagData = null;

        let isEmpty = false;
        // dataTag becomes empty, remove it from the rendering
        if (tagData.numInstances === 0) {
            tagData.clear();
            this._instanceDataMap.delete(tag);
            for (let i = 0; this._instanceDataMapValues.length; i++) {
                if (this._instanceDataMapValues[i].numInstances === 0) {
                    this._instanceDataMapValues.splice(i, 1);
                    break;
                }
            }
            this._clearDataTagQueue();
            isEmpty = true;
        }

        if (!isEmpty) {
            tagData.refresh();
            this._updateTag(tagData);
        }
    }

    /*
    public _removeGeoObject(geoObject: GeoObject) {

        let tagData = geoObject._tagData!;
        let tag = geoObject.tag;
        let tdi = geoObject._tagDataIndex;

        this._geoObjects.splice(geoObject._handlerIndex, 1);
        for (let i = geoObject._handlerIndex, len = this._geoObjects.length; i < len; i++) {
            let gi = this._geoObjects[i];
            gi._handlerIndex = gi._handlerIndex - 1;
        }

        if (tdi < tagData._opaqueInstanceCount) {
            tagData._opaqueInstanceCount--;
        }

        tagData.geoObjects.splice(tdi, 1);
        for (let i = tdi, len = tagData.geoObjects.length; i < len; i++) {
            let gi = tagData.geoObjects[i];
            gi._tagDataIndex = gi._tagDataIndex - 1;
        }

        tagData._rgbaArr = spliceArray(tagData._rgbaArr, tdi * 4, 4);
        tagData._rtcPositionHighArr = spliceArray(tagData._rtcPositionHighArr, tdi * 3, 3);
        tagData._rtcPositionLowArr = spliceArray(tagData._rtcPositionLowArr, tdi * 3, 3);
        tagData._qRotArr = spliceArray(tagData._qRotArr, tdi * 4, 4);
        tagData._pickingColorArr = spliceArray(tagData._pickingColorArr, tdi * 3, 3);
        tagData._sizeArr = spliceArray(tagData._sizeArr, tdi * 3, 3);
        tagData._translateArr = spliceArray(tagData._translateArr, tdi * 3, 3);
        tagData._localPositionArr = spliceArray(tagData._localPositionArr, tdi * 3, 3);
        tagData._visibleArr = spliceArray(tagData._visibleArr, tdi, 1);
        tagData.numInstances--;

        geoObject._handlerIndex = -1;
        geoObject._handler = null;

        geoObject._tagDataIndex = -1;
        geoObject._tagData = null;

        let isEmpty = false;
        // dataTag becomes empty, remove it from the rendering
        if (tagData.numInstances === 0) {
            tagData.clear();
            this._instanceDataMap.delete(tag);
            for (let i = 0; this._instanceDataMapValues.length; i++) {
                if (this._instanceDataMapValues[i].numInstances === 0) {
                    this._instanceDataMapValues.splice(i, 1);
                    break;
                }
            }
            this._clearDataTagQueue();
            isEmpty = true;
        }

        if (!isEmpty) {
            tagData.refresh();
            this._updateTag(tagData);
        }
    }
    */
}