entity/polyline/PolylineHandler.tstypescript
import * as shaders from "../../shaders/polyline/polyline";
import { EntityCollection } from "../EntityCollection";
import { Polyline } from "./Polyline";
import { Renderer } from "../../renderer/Renderer";
import { Scene } from "../../scene/Scene";
import { Vec3 } from "../../math/Vec3";
import { PolylineBatchRenderer } from "./PolylineBatchRenderer";

class PolylineHandler {
    static __counter__: number = 0;
    protected __id: number;
    public _entityCollection: EntityCollection;
    public pickingEnabled: boolean;
    protected _renderer: Renderer | null;
    public _relativeCenter: Vec3;
    public _rtcEyePositionHigh: Float32Array;
    public _rtcEyePositionLow: Float32Array;

    public _opaqueRenderer: PolylineBatchRenderer;
    public _transparentRenderer: PolylineBatchRenderer;
    public _opaqueTexRenderer: PolylineBatchRenderer;
    public _transparentTexRenderer: PolylineBatchRenderer;

    protected _polylines: Polyline[] = [];

    constructor(entityCollection: EntityCollection) {
        this.__id = PolylineHandler.__counter__++;

        this._entityCollection = entityCollection;

        this._renderer = null;

        this._polylines = [];

        this._opaqueRenderer = new PolylineBatchRenderer(this, {
            path3v: []
            //isTextured: true,
        });

        this._transparentRenderer = new PolylineBatchRenderer(this, {
            path3v: []
            //isTextured: true,
        });

        this._opaqueTexRenderer = new PolylineBatchRenderer(this, {
            path3v: [],
            isTextured: true
        });

        this._transparentTexRenderer = new PolylineBatchRenderer(this, {
            path3v: [],
            isTextured: true
        });

        this.pickingEnabled = true;

        this._relativeCenter = new Vec3();

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

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

        const rtcProject = this.getRTCPosition.bind(this);
        this._opaqueRenderer.__doubleToTwoFloats = rtcProject;
        this._transparentRenderer.__doubleToTwoFloats = rtcProject;
        this._opaqueTexRenderer.__doubleToTwoFloats = rtcProject;
        this._transparentTexRenderer.__doubleToTwoFloats = rtcProject;
    }

    public setVisibleSphere(p: Vec3, r: number) {
        this._opaqueRenderer.setVisibleSphere(p, r);
        this._transparentRenderer.setVisibleSphere(p, r);
        this._opaqueTexRenderer.setVisibleSphere(p, r);
        this._transparentTexRenderer.setVisibleSphere(p, r);
    }

    /**
     * Requests the next frame to be rendered.
     * @public
     */
    public requestRedraw() {
        this._renderer && (this._renderer.handler.needRedraw = true);
    }

    protected _initProgram() {
        if (!this._renderer) return;

        this._renderer.addPrograms(shaders.polylineTex(), shaders.polylinePlain(), shaders.polyline_picking());
        if (!this._renderer.deferredDisabled) {
            this._renderer.addPrograms(shaders.polylineTexWoit(), shaders.polylineWoitPlain());
        }
    }

    public bindScene(scene: Scene) {
        this._renderer = scene.renderer;
        this._initProgram();

        this._opaqueRenderer.bindScene(scene);
        this._transparentRenderer.bindScene(scene);

        this._opaqueTexRenderer.bindScene(scene);
        this._transparentTexRenderer.bindScene(scene);
    }

    public add(polyline: Polyline) {
        if (polyline._handlerIndex === -1) {
            const batchRenderer = this.getRenderer(polyline.getOpacity(), polyline.getSrc() != null);
            polyline._handler = this;
            polyline._handlerIndex = this._polylines.length;
            polyline._batchRenderer = batchRenderer;
            this._polylines.push(polyline);

            if (polyline.getVisibility()) {
                polyline._addToBatchRenderer();
            }

            if (this._entityCollection && this._entityCollection.scene) {
                batchRenderer.updateRTCPosition();
            }
        }
    }

    public remove(polyline: Polyline) {
        let index = polyline._handlerIndex;
        if (index !== -1) {
            polyline._removeFromBatchRenderer();
            polyline._handlerIndex = -1;
            polyline._handler = null;
            polyline._batchRenderer = null;
            this._polylines.splice(index, 1);
            this.reindexPolylineArray(index);
        }
    }

    public getRenderer(opacity: number, textured: boolean): PolylineBatchRenderer {
        if (textured) {
            return opacity < 1.0 ? this._transparentTexRenderer : this._opaqueTexRenderer;
        }
        return opacity < 1.0 ? this._transparentRenderer : this._opaqueRenderer;
    }

    public reindexAfterRemoval(removedBatchIndex: number, renderer: PolylineBatchRenderer) {
        for (let p = 0; p < this._polylines.length; p++) {
            if (this._polylines[p]._batchRenderer !== renderer) {
                continue;
            }
            const indices = this._polylines[p]._batchRendererIndexes;
            for (let i = 0; i < indices.length; i++) {
                if (indices[i] > removedBatchIndex) {
                    indices[i]--;
                }
            }
        }
    }

    public reindexPolylineArray(startIndex: number) {
        let ls = this._polylines;
        for (let i = startIndex; i < ls.length; i++) {
            ls[i]._handlerIndex = i;
        }
    }

    public drawForward() {
        this.drawOpaque();
    }

    public drawOpaque() {
        this._updateRTCEyePosition();
        this._opaqueRenderer.drawOpaque();
        this._opaqueTexRenderer.drawOpaque();
    }

    public drawTransparent() {
        this._updateRTCEyePosition();
        this._transparentRenderer.drawTransparent();
        this._transparentTexRenderer.drawTransparent();
    }

    public drawTransparentForward() {
        this._updateRTCEyePosition();
        this._transparentRenderer.drawOpaque();
        this._transparentTexRenderer.drawOpaque();
    }

    public drawPicking() {
        if (this.pickingEnabled) {
            this._opaqueRenderer.drawPicking();
            this._transparentRenderer.drawPicking();
            this._opaqueTexRenderer.drawPicking();
            this._transparentTexRenderer.drawPicking();
        }
    }

    public clear() {
        //
        // remove lines
        let i = this._polylines.length;
        while (i--) {
            this._polylines[i]._handler = null;
            this._polylines[i]._handlerIndex = -1;
            this._polylines[i]._batchRenderer = null;
            this._polylines[i]._batchRendererIndexes.length = 0;
        }
        this._polylines.length = 0;
        this._polylines = [];

        // clear renderers
        this._opaqueRenderer.clear();
        this._transparentRenderer.clear();
        this._opaqueTexRenderer.clear();
        this._transparentTexRenderer.clear();
    }

    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);
        this._opaqueRenderer.updateRTCPosition();
        this._transparentRenderer.updateRTCPosition();
        this._opaqueTexRenderer.updateRTCPosition();
        this._transparentTexRenderer.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 reloadTextures() {
        for (let i = 0; i < this._polylines.length; i++) {
            let ri = this._polylines[i];
            ri.setSrc(ri.getSrc());
        }
    }

    public get polylines(): Polyline[] {
        return [...this._polylines];
    }

    protected _updateTexCoords(renderer: PolylineBatchRenderer) {
        if (!this._renderer) return;
        const ta = this._renderer.strokeTextureAtlas;
        const img = renderer.getImage();
        const tc: (number[] | null)[] = [];
        for (let j = 0; j < img.length; j++) {
            const m = img[j];
            const d = m?.__nodeIndex != null ? ta.get(m.__nodeIndex) : null;
            tc[j] = d?.texCoords ?? null;
        }
        if (tc.length) renderer._setTexCoordArr(tc);
    }

    public refreshTexCoordsArr() {
        const bc = this._entityCollection;
        if (!bc || !this._renderer) return;
        for (let i = 0; i < this._polylines.length; i++) {
            this._updateTexCoords(this._opaqueTexRenderer);
            this._updateTexCoords(this._transparentTexRenderer);
        }
    }
}

export { PolylineHandler };