utils/VectorTileCreator.tstypescript
import { doubleToTwoFloats2 } from "../math/coder";
import { Framebuffer } from "../webgl/Framebuffer";
import { Handler } from "../webgl/Handler";
import { Material } from "../layer/Material";
import { ShaderProgram } from "../webgl/ShaderProgram";
import { Planet } from "../scene/Planet";
import { NOTRENDERING } from "../quadTree/quadTree";
import { Vector } from "../layer/Vector";

let tempArr = new Float32Array(2);

const MAX_FRAME_TIME = 50.0;

export class VectorTileCreator {
    protected _width: number;
    protected _height: number;
    protected _planet: Planet;
    protected _framebuffer: Framebuffer | null;
    protected _queue: Material[];
    protected _handler: Handler | null;

    constructor(planet: Planet, width: number = 256, height: number = 256) {
        this._width = width;
        this._height = height;
        this._planet = planet;
        this._framebuffer = null;
        this._queue = [];
        this._handler = null;
    }

    public init() {
        this._handler = this._planet.renderer!.handler;

        //Line
        if (!this._handler.programs.vectorTileLineRasterization) {
            this._handler.addProgram(
                new ShaderProgram("vectorTileLineRasterization", {
                    uniforms: {
                        viewport: "vec2",
                        thicknessOutline: "float",
                        alpha: "float",
                        extentParamsHigh: "vec4",
                        extentParamsLow: "vec4"
                    },
                    attributes: {
                        prevHigh: "vec2",
                        currentHigh: "vec2",
                        nextHigh: "vec2",

                        prevLow: "vec2",
                        currentLow: "vec2",
                        nextLow: "vec2",

                        order: "float",
                        color: "vec4",
                        thickness: "float"
                    },
                    vertexShader: `attribute vec2 prevHigh;
                attribute vec2 currentHigh;
                attribute vec2 nextHigh;

                attribute vec2 prevLow;
                attribute vec2 currentLow;
                attribute vec2 nextLow;

                attribute float order;
                attribute float thickness;
                attribute vec4 color;
                uniform float thicknessOutline;
                uniform vec2 viewport;
                uniform vec4 extentParamsHigh;
                uniform vec4 extentParamsLow;
                varying vec4 vColor;

                vec2 proj(vec2 coordHigh, vec2 coordLow) {
                    vec2 highDiff = coordHigh - extentParamsHigh.xy;
                    vec2 lowDiff = coordLow - extentParamsLow.xy;
                    return vec2(-1.0 + (highDiff * step(1.0, length(highDiff)) + lowDiff) * extentParamsHigh.zw) * vec2(1.0, -1.0);
                }

                vec2 getIntersection(vec2 start1, vec2 end1, vec2 start2, vec2 end2, float eps) {
                    vec2 dir = end2 - start2;
                    vec2 perp = vec2(-dir.y, dir.x);
                    float d2 = dot(perp, start2);
                    float seg = dot(perp, start1) - d2;
                    float prl = seg - dot(perp, end1) + d2;
                    if (prl > -eps && prl < eps) {
                        return start1;
                    }
                    float u = seg / prl;
                    return start1 + u * (end1 - start1);
                }

                vec2 safeNormalize(vec2 v, vec2 fallback, float eps) {
                    float len2 = dot(v, v);
                    if (len2 > eps * eps) {
                        return v * inversesqrt(len2);
                    }
                    return fallback;
                }

                void main(){
                    vColor = color;
                    float eps = 2.0 / max(viewport.x, viewport.y);

                    vec2 vNext = proj(nextHigh, nextLow),
                         vCurrent = proj(currentHigh, currentLow),
                         vPrev = proj(prevHigh, prevLow);

                    vec2 _next = vNext;
                    vec2 _prev = vPrev;
                    vec2 _current = vCurrent;

                    if(distance(_prev, _current) < eps){
                        if(distance(_next, _current) < eps){
                            _next = _current + vec2(1.0, 0.0);
                            _prev = _current - vec2(1.0, 0.0);
                        }else{
                            _prev = _current + safeNormalize(_current - _next, vec2(1.0, 0.0), eps);
                        }
                    }

                    if(distance(_next, _current) < eps){
                        _next = _current + safeNormalize(_current - _prev, vec2(-1.0, 0.0), eps);
                    }

                    vec2 sNext = _next;
                    vec2 sCurrent = _current;
                    vec2 sPrev = _prev;

                    vec2 dirNext = safeNormalize(sNext - sCurrent, vec2(1.0, 0.0), eps);
                    vec2 dirPrev = safeNormalize(sPrev - sCurrent, -dirNext, eps);
                    float dotNP = clamp(dot(dirNext, dirPrev), -1.0, 1.0);

                    vec2 normalNext = safeNormalize(vec2(-dirNext.y, dirNext.x), vec2(0.0, 1.0), eps);
                    vec2 normalPrev = safeNormalize(vec2(dirPrev.y, -dirPrev.x), vec2(0.0, 1.0), eps);
                    vec2 sideNormal = abs(order) == 1.0 ? normalPrev : normalNext;
                    float side = sign(order);
                    vec2 d = (thickness + thicknessOutline) * 0.5 * side / viewport;

                    vec2 m;
                    float lenNext = distance(sCurrent, sNext);
                    float lenPrev = distance(sCurrent, sPrev);
                    if(lenNext < eps || lenPrev < eps){
                        m = sCurrent + sideNormal * d;
                    }else if(dotNP >= 0.99991){
                        m = sCurrent - normalPrev * d;
                    }else{
                        m = getIntersection(sCurrent + normalPrev * d, sPrev + normalPrev * d,
                                            sCurrent + normalNext * d, sNext + normalNext * d, eps);

                        if( dotNP > 0.5 && dot(dirNext + dirPrev, m - sCurrent) < 0.0 ){
                            float occw = order * sign(dirNext.x * dirPrev.y - dirNext.y * dirPrev.x);
                            if(occw == -1.0){
                                m = sCurrent + normalPrev * d;
                            }else if(occw == 1.0){
                                m = sCurrent + normalNext * d;
                            }else if(occw == -2.0){
                                m = sCurrent + normalNext * d;
                            }else if(occw == 2.0){
                                m = sCurrent + normalPrev * d;
                            }
                        }else if(distance(sCurrent, m) > min(distance(sCurrent, sNext), distance(sCurrent, sPrev))){
                            m = sCurrent + sideNormal * d;
                        }
                    }
                    float sameSide = dot(m - sCurrent, sideNormal) * side;
                    float wrongSide = 1.0 - step(0.0, sameSide);
                    m = mix(m, sCurrent + sideNormal * d, wrongSide);
                    gl_Position = vec4(m.x, m.y, 0.0, 1.0);
                }`,
                    fragmentShader: `precision highp float;
                uniform float alpha;
                varying vec4 vColor;
                void main() {
                    gl_FragColor = vec4(vColor.rgb, alpha * vColor.a);
                }`
                })
            );
        }

        //Polygon
        if (!this._handler.programs.vectorTilePolygonRasterization) {
            this._handler.addProgram(
                new ShaderProgram("vectorTilePolygonRasterization", {
                    uniforms: {
                        extentParamsHigh: "vec4",
                        extentParamsLow: "vec4"
                    },
                    attributes: {
                        coordinatesHigh: "vec2",
                        coordinatesLow: "vec2",
                        colors: "vec4"
                    },
                    vertexShader: `attribute vec2 coordinatesHigh;
                attribute vec2 coordinatesLow;
                attribute vec4 colors;
                uniform vec4 extentParamsHigh;
                uniform vec4 extentParamsLow;
                varying vec4 color;

                vec2 proj(vec2 coordHigh, vec2 coordLow) {
                    vec2 highDiff = coordHigh - extentParamsHigh.xy;
                    vec2 lowDiff = coordLow - extentParamsLow.xy;
                    return vec2(-1.0 + (highDiff * step(1.0, length(highDiff)) + lowDiff) * extentParamsHigh.zw) * vec2(1.0, -1.0);
                }

                void main() {
                    color = colors;
                    gl_Position = vec4(proj(coordinatesHigh, coordinatesLow), 0.0, 1.0);
                }`,
                    fragmentShader: `precision highp float;
                varying vec4 color;
                void main () {
                    gl_FragColor = color;
                }`
                })
            );
        }

        this._framebuffer = new Framebuffer(this._handler, {
            width: this._width,
            height: this._height,
            useDepth: false
        });

        this._framebuffer.init();
    }

    /**
     * Returns true when there is nothing left to rasterize.
     * @public
     * @returns {boolean}
     */
    public get isIdle(): boolean {
        return this._queue.length === 0;
    }

    public frame() {
        if (this._planet.layerLock.isFree() && this._queue.length) {
            let h = this._handler!,
                gl = h.gl!;

            gl.disable(gl.CULL_FACE);
            gl.disable(gl.DEPTH_TEST);

            let hLine = h.programs.vectorTileLineRasterization,
                hPoly = h.programs.vectorTilePolygonRasterization;

            let _w = this._width,
                _h = this._height,
                width = _w,
                height = _h,
                _w2 = width << 1,
                _h2 = height << 1;

            // var prevLayerId = -1;

            let extentParamsHigh = new Float32Array(4);
            let extentParamsLow = new Float32Array(4);

            let f = this._framebuffer!.activate();

            let deltaTime = 0,
                startTime = window.performance.now();

            while (this._queue.length && deltaTime < MAX_FRAME_TIME) {
                let material = this._queue.shift()!;
                if (material.isLoading && material.segment.node.getState() !== NOTRENDERING) {
                    let pickingEnabled = material.layer._pickingEnabled;

                    if (material.segment.tileZoom < 4) {
                        width = _w2;
                        height = _h2;
                    } else {
                        width = _w;
                        height = _h;
                    }

                    let texture = material._updateTexture || h.createEmptyTexture_l(width, height);
                    let pickingMask = pickingEnabled
                        ? material._updatePickingMask || h.createEmptyTexture_n(width, height)
                        : null;

                    material.applyTexture(texture, pickingMask);

                    f.setSize(width, height);

                    f.bindOutputTexture(texture!);

                    gl.clearColor(0.0, 0.0, 0.0, 0.0);
                    gl.clear(gl.COLOR_BUFFER_BIT);

                    let extent = material.segment.getExtentMerc();

                    doubleToTwoFloats2(extent.southWest.lon, tempArr);
                    extentParamsHigh[0] = tempArr[0];
                    extentParamsLow[0] = tempArr[1];

                    doubleToTwoFloats2(extent.southWest.lat, tempArr);
                    extentParamsHigh[1] = tempArr[0];
                    extentParamsLow[1] = tempArr[1];

                    extentParamsHigh[2] = 2.0 / extent.getWidth();
                    extentParamsHigh[3] = 2.0 / extent.getHeight();

                    hPoly.activate();
                    let sh = hPoly;
                    let sha = sh.attributes,
                        shu = sh.uniforms;

                    let geomHandler = (material.layer as Vector)._geometryHandler;

                    //=========================================
                    //Polygon rendering
                    //=========================================
                    gl.uniform4fv(shu.extentParamsHigh, extentParamsHigh);
                    gl.uniform4fv(shu.extentParamsLow, extentParamsLow);

                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyVerticesHighBufferMerc!);
                    gl.vertexAttribPointer(
                        sha.coordinatesHigh,
                        geomHandler._polyVerticesHighBufferMerc!.itemSize,
                        gl.FLOAT,
                        false,
                        0,
                        0
                    );

                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyVerticesLowBufferMerc!);
                    gl.vertexAttribPointer(
                        sha.coordinatesLow,
                        geomHandler._polyVerticesLowBufferMerc!.itemSize,
                        gl.FLOAT,
                        false,
                        0,
                        0
                    );

                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyColorsBuffer!);
                    gl.vertexAttribPointer(sha.colors, geomHandler._polyColorsBuffer!.itemSize, gl.FLOAT, false, 0, 0);

                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geomHandler._polyIndexesBuffer!);

                    gl.drawElements(gl.TRIANGLES, geomHandler._polyIndexesBuffer!.numItems, gl.UNSIGNED_INT, 0);

                    //Polygon picking PASS
                    if (pickingEnabled) {
                        f.bindOutputTexture(pickingMask!);

                        gl.clearColor(0.0, 0.0, 0.0, 0.0);
                        gl.clear(gl.COLOR_BUFFER_BIT);

                        gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyPickingColorsBuffer!);
                        gl.vertexAttribPointer(
                            sha.colors,
                            geomHandler._polyPickingColorsBuffer!.itemSize,
                            gl.FLOAT,
                            false,
                            0,
                            0
                        );

                        gl.drawElements(gl.TRIANGLES, geomHandler._polyIndexesBuffer!.numItems, gl.UNSIGNED_INT, 0);
                    }

                    //=========================================
                    //Strokes and linestrings rendering
                    //=========================================
                    f.bindOutputTexture(texture!);

                    hLine.activate();
                    sh = hLine;
                    sha = sh.attributes;
                    shu = sh.uniforms;

                    gl.uniform2fv(shu.viewport, [width, height]);

                    gl.uniform4fv(shu.extentParamsHigh, extentParamsHigh);
                    gl.uniform4fv(shu.extentParamsLow, extentParamsLow);

                    //vertex
                    let mb = geomHandler._lineVerticesHighBufferMerc!;
                    gl.bindBuffer(gl.ARRAY_BUFFER, mb);

                    gl.vertexAttribPointer(sha.prevHigh, mb.itemSize, gl.FLOAT, false, 8, 0);
                    gl.vertexAttribPointer(sha.currentHigh, mb.itemSize, gl.FLOAT, false, 8, 32);
                    gl.vertexAttribPointer(sha.nextHigh, mb.itemSize, gl.FLOAT, false, 8, 64);

                    mb = geomHandler._lineVerticesLowBufferMerc!;
                    gl.bindBuffer(gl.ARRAY_BUFFER, mb);

                    gl.vertexAttribPointer(sha.prevLow, mb.itemSize, gl.FLOAT, false, 8, 0);
                    gl.vertexAttribPointer(sha.currentLow, mb.itemSize, gl.FLOAT, false, 8, 32);
                    gl.vertexAttribPointer(sha.nextLow, mb.itemSize, gl.FLOAT, false, 8, 64);

                    //order
                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineOrdersBuffer!);
                    gl.vertexAttribPointer(sha.order, geomHandler._lineOrdersBuffer!.itemSize, gl.FLOAT, false, 4, 0);

                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geomHandler._lineIndexesBuffer!);

                    //PASS - stroke
                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineStrokesBuffer!);
                    gl.vertexAttribPointer(
                        sha.thickness,
                        geomHandler._lineStrokesBuffer!.itemSize,
                        gl.FLOAT,
                        false,
                        0,
                        0
                    );

                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineStrokeColorsBuffer!);
                    gl.vertexAttribPointer(
                        sha.color,
                        geomHandler._lineStrokeColorsBuffer!.itemSize,
                        gl.FLOAT,
                        false,
                        0,
                        0
                    );

                    //Antialias pass
                    gl.uniform1f(shu.thicknessOutline, 2);
                    gl.uniform1f(shu.alpha, 0.54);
                    gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer!.numItems, gl.UNSIGNED_INT, 0);
                    //
                    //Aliased pass
                    gl.uniform1f(shu.thicknessOutline, 1);
                    gl.uniform1f(shu.alpha, 1.0);
                    gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer!.numItems, gl.UNSIGNED_INT, 0);

                    //PASS - inside line
                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineThicknessBuffer!);
                    gl.vertexAttribPointer(
                        sha.thickness,
                        geomHandler._lineThicknessBuffer!.itemSize,
                        gl.FLOAT,
                        false,
                        0,
                        0
                    );

                    gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineColorsBuffer!);
                    gl.vertexAttribPointer(sha.color, geomHandler._lineColorsBuffer!.itemSize, gl.FLOAT, false, 0, 0);

                    //Antialias pass
                    gl.uniform1f(shu.thicknessOutline, 2);
                    gl.uniform1f(shu.alpha, 0.54);
                    gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer!.numItems, gl.UNSIGNED_INT, 0);
                    //
                    //Aliased pass
                    gl.uniform1f(shu.thicknessOutline, 1);
                    gl.uniform1f(shu.alpha, 1.0);
                    gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer!.numItems, gl.UNSIGNED_INT, 0);

                    if (pickingEnabled) {
                        f.bindOutputTexture(pickingMask!);
                        gl.uniform1f(shu.thicknessOutline, 8);
                        gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._linePickingColorsBuffer!);
                        gl.vertexAttribPointer(
                            sha.color,
                            geomHandler._linePickingColorsBuffer!.itemSize,
                            gl.FLOAT,
                            false,
                            0,
                            0
                        );
                        gl.drawElements(
                            gl.TRIANGLE_STRIP,
                            geomHandler._lineIndexesBuffer!.numItems,
                            gl.UNSIGNED_INT,
                            0
                        );
                    }
                } else {
                    material.isLoading = false;
                }

                deltaTime = window.performance.now() - startTime;
            }

            gl.enable(gl.DEPTH_TEST);
            gl.enable(gl.CULL_FACE);

            f.deactivate();
        }
    }

    public add(material: Material) {
        this._queue.push(material);
    }

    public remove(material: Material) {
        //...
    }

    public get queueSize(): number {
        return this._queue.length;
    }
}