entity/strip/StripHandler.tstypescript
import { EntityCollection } from "../EntityCollection";
import { Renderer } from "../../renderer/Renderer";
import { Scene } from "../../scene/Scene";
import { Strip } from "./Strip";
import { stripForward, stripTransparent } from "../../shaders/strip/strip";

class StripHandler {
    static __counter__: number = 0;
    protected __id: number;

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

    /**
     * Parent collection
     * @protected
     * @type {EntityCollection}
     */
    protected _entityCollection: EntityCollection;

    /**
     * Renderer
     * @protected
     * @type {Renderer | null}
     */
    protected _renderer: Renderer | null;

    /**
     * Strip objects array
     * @protected
     * @type {Array.<Strip>}
     */
    protected _strips: Strip[];

    protected _opaqueCount: number;

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

        this.pickingEnabled = true;

        this._entityCollection = entityCollection;

        this._renderer = null;

        this._strips = [];
        this._opaqueCount = 0;
    }

    protected _initProgram() {
        if (this._renderer && this._renderer.handler) {
            !this._renderer.handler.programs.stripForward && this._renderer.handler.addProgram(stripForward());
            if (!this._renderer.deferredDisabled) {
                !this._renderer.handler.programs.stripTransparent &&
                    this._renderer.handler.addProgram(stripTransparent());
            }
        }
    }

    public bindScene(scene: Scene) {
        this._renderer = scene.renderer;
        this._initProgram();
        for (let i = 0; i < this._strips.length; i++) {
            this._strips[i].bindScene(scene);
        }
    }

    protected _swap(i: number, j: number) {
        if (i === j) return;
        const a = this._strips;
        const ti = a[i];
        const tj = a[j];
        a[i] = tj;
        a[j] = ti;
        tj._handlerIndex = i;
        ti._handlerIndex = j;
    }

    public add(strip: Strip) {
        if (strip._handlerIndex === -1) {
            strip._handler = this;
            const index = this._strips.length;
            strip._handlerIndex = index;
            this._strips.push(strip);

            // keep opaque first, transparent last
            if (strip.isOpaque()) {
                this._swap(index, this._opaqueCount);
                this._opaqueCount++;
            }
            this._entityCollection && this._entityCollection.scene && strip.bindScene(this._entityCollection.scene);
        }
    }

    public remove(strip: Strip) {
        const index = strip._handlerIndex;
        if (index === -1) return;

        strip._deleteBuffers();

        const a = this._strips;
        const lastIndex = a.length - 1;
        if (index < 0 || index > lastIndex) {
            strip._handlerIndex = -1;
            strip._handler = null;
            return;
        }

        if (index < this._opaqueCount) {
            const lastOpaqueIndex = this._opaqueCount - 1;
            this._swap(index, lastOpaqueIndex);
            this._opaqueCount--;
            this._swap(lastOpaqueIndex, lastIndex);
        } else {
            this._swap(index, lastIndex);
        }

        a.pop();

        strip._handlerIndex = -1;
        strip._handler = null;
    }

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

    public drawOpaque(): void {
        for (let i = 0; i < this._opaqueCount; i++) {
            this._strips[i].drawOpaque();
        }
    }

    public drawTransparent(): void {
        for (let i = this._opaqueCount; i < this._strips.length; i++) {
            this._strips[i].drawTransparent();
        }
    }

    public drawTransparentForward(): void {
        for (let i = this._opaqueCount; i < this._strips.length; i++) {
            this._strips[i].drawOpaque();
        }
    }

    public updateStripOpacity(strip: Strip) {
        const index = strip._handlerIndex;
        if (index === -1) return;

        const toOpaque = strip.isOpaque();
        const isOpaque = index < this._opaqueCount;
        if (toOpaque === isOpaque) return;

        if (toOpaque) {
            this._swap(index, this._opaqueCount);
            this._opaqueCount++;
        } else {
            const lastOpaqueIndex = this._opaqueCount - 1;
            this._swap(index, lastOpaqueIndex);
            this._opaqueCount--;
        }
    }

    public drawPicking() {
        if (this.pickingEnabled) {
            let i = this._strips.length;
            while (i--) {
                this._strips[i].drawPicking();
            }
        }
    }

    public clear() {
        let i = this._strips.length;
        while (i--) {
            this._strips[i]._deleteBuffers();
            this._strips[i]._handler = null;
            this._strips[i]._handlerIndex = -1;
        }
        this._strips.length = 0;
        this._strips = [];
        this._opaqueCount = 0;
    }
}

export { StripHandler };