Source: og/entity/Strip.js

"use strict";

import * as utils from "../utils/shared.js";
import { Vec3 } from "../math/Vec3.js";
import { Line3 } from "../math/Line3.js";

let _tempHigh = new Vec3(),
    _tempLow = new Vec3();

/**
 * Strip object.
 * @class
 * @param {*} [options] - Strip options:
 * @param {boolean} [options.visibility] - Strip visibility.
 * @example <caption>Stripe example</caption>
 * new og.Entity({
 *     strip: {
 *         gridSize: 10,
 *         path: [
 *             [[],[]],
 *             [[],[]]
 *         ]
 *     }
 * });
 */
class Strip {
    constructor(options) {
        options = options || {};

        /**
         * Object unic identifier.
         * @public
         * @readonly
         * @type {number}
         */
        this.id = Strip._staticCounter++;

        /**
         * Cloud visibility.
         * @public
         * @type {boolean}
         */
        this.visibility = options.visibility != undefined ? options.visibility : true;

        this.color = new Float32Array([1.0, 1.0, 1.0, 0.5]);

        if (options.color) {
            let color = utils.createColorRGBA(options.color);
            this.setColor(color.x, color.y, color.z, color.w);
        }

        if (options.opacity) {
            this.setOpacity(options.opacity);
        }

        /**
         * Parent collection render node.
         * @private
         * @type {RenderNode}
         */
        this._renderNode = null;

        /**
         * Entity instance that holds this strip.
         * @private
         * @type {Entity}
         */
        this._entity = null;

        this._verticesHighBuffer = null;
        this._verticesLowBuffer = null;

        this._indexesBuffer = null;

        this._verticesHigh = [];
        this._verticesLow = [];

        this._indexes = [];

        this._path = [];

        this._pickingColor = new Float32Array(4);

        this._gridSize = 1;

        /**
         * Handler that stores and renders this object.
         * @private
         * @type {StripHandler}
         */
        this._handler = null;
        this._handlerIndex = -1;

        if (options.path) {
            this.setPath(options.path);
        }
    }

    static get _staticCounter() {
        if (!this._counter && this._counter !== 0) {
            this._counter = 0;
        }
        return this._counter;
    }

    static set _staticCounter(n) {
        this._counter = n;
    }

    /**
     * Assign picking color.
     * @protected
     * @param {Vec3} color - Picking RGB color.
     */
    setPickingColor3v(color) {
        this._pickingColor[0] = color.x / 255.0;
        this._pickingColor[1] = color.y / 255.0;
        this._pickingColor[2] = color.z / 255.0;
        this._pickingColor[3] = 1.0;
    }

    /**
     * Clears object
     * @public
     */
    clear() {
        this._path.length = 0;
        this._path = [];

        this._verticesHigh.length = 0;
        this._verticesHigh = [];

        this._verticesLow.length = 0;
        this._verticesLow = [];

        this._indexes.length = 0;
        this._indexes = [];

        this._deleteBuffers();
    }

    setColor(r, g, b, a) {
        a = a || this.color[3];
        this.color[0] = r;
        this.color[1] = g;
        this.color[2] = b;
        this.color[3] = a;
    }

    /**
     * Set strip opacity.
     * @public
     * @param {number} opacity - opacity.
     */
    setOpacity(opacity) {
        this.color[3] = opacity || 0;
    }

    /**
     * Sets cloud visibility.
     * @public
     * @param {number} visibility - Visibility flag.
     */
    setVisibility(visibility) {
        this.visibility = visibility;
    }

    /**
     * @return {boolean} Strip visibily.
     */
    getVisibility() {
        return this.visibility;
    }

    /**
     * Assign rendering scene node.
     * @public
     * @param {RenderNode}  renderNode - Assigned render node.
     */
    setRenderNode(renderNode) {
        this._renderNode = renderNode;
        this._createBuffers();
    }

    /**
     * Removes from entity.
     * @public
     */
    remove() {
        this._entity = null;
        this._handler && this._handler.remove(this);
    }

    draw() {
        if (this.visibility && this._verticesHigh.length) {
            var r = this._renderNode.renderer;

            var gl = r.handler.gl;

            var sh = r.handler.programs.strip,
                p = sh._program,
                sha = p.attributes,
                shu = p.uniforms;

            gl.disable(gl.CULL_FACE);
            gl.blendEquation(gl.FUNC_ADD);
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
            gl.enable(gl.BLEND);
            sh.activate();

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

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

            gl.uniform4fv(shu.uColor, this.color);
            gl.uniform1f(shu.uOpacity, this._entity._entityCollection._fadingOpacity);
            gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesHighBuffer);
            gl.vertexAttribPointer(
                sha.aVertexPositionHigh,
                this._verticesHighBuffer.itemSize,
                gl.FLOAT,
                false,
                0,
                0
            );
            gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesLowBuffer);
            gl.vertexAttribPointer(
                sha.aVertexPositionLow,
                this._verticesLowBuffer.itemSize,
                gl.FLOAT,
                false,
                0,
                0
            );
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
            gl.drawElements(
                r.handler.gl.TRIANGLE_STRIP,
                this._indexBuffer.numItems,
                gl.UNSIGNED_INT,
                0
            );
            gl.enable(gl.CULL_FACE);
        }
    }

    drawPicking() {
        if (this.visibility && this._verticesHigh.length) {
            var r = this._renderNode.renderer;

            var gl = r.handler.gl;

            var sh = r.handler.programs.strip,
                p = sh._program,
                sha = p.attributes,
                shu = p.uniforms;

            gl.disable(gl.CULL_FACE);
            gl.blendEquation(gl.FUNC_ADD);
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
            gl.enable(gl.BLEND);
            sh.activate();

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

            gl.uniform3fv(shu.eyePositionHigh, r.activeCamera.eyeHigh);
            gl.uniform3fv(shu.eyePositionLow, r.activeCamera.eyeLow);
            gl.uniform1f(shu.uOpacity, this._entity._entityCollection._fadingOpacity != 0 ? 1 : 0);

            gl.uniform4fv(shu.uColor, this._pickingColor);
            gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesHighBuffer);
            gl.vertexAttribPointer(
                sha.aVertexPositionHigh,
                this._verticesHighBuffer.itemSize,
                gl.FLOAT,
                false,
                0,
                0
            );
            gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesLowBuffer);
            gl.vertexAttribPointer(
                sha.aVertexPositionLow,
                this._verticesLowBuffer.itemSize,
                gl.FLOAT,
                false,
                0,
                0
            );
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
            gl.drawElements(
                r.handler.gl.TRIANGLE_STRIP,
                this._indexBuffer.numItems,
                gl.UNSIGNED_INT,
                0
            );
            gl.enable(gl.CULL_FACE);
        }
    }

    /**
     * Delete buffers
     * @private
     */
    _deleteBuffers() {
        if (this._renderNode && this._renderNode.renderer) {
            var r = this._renderNode.renderer,
                gl = r.handler.gl;

            gl.deleteBuffer(this._indexBuffer);
            gl.deleteBuffer(this._verticesHighBuffer);
            gl.deleteBuffer(this._verticesLowBuffer);
        }
        this._verticesHighBuffer = null;
        this._verticesLowBuffer = null;
        this._indexBuffer = null;
    }

    _createBuffers() {
        if (this._renderNode && this._renderNode.renderer) {
            var gl = this._renderNode.renderer.handler.gl;

            gl.deleteBuffer(this._indexBuffer);
            gl.deleteBuffer(this._verticesHighBuffer);
            gl.deleteBuffer(this._verticesLowBuffer);

            this._verticesHighBuffer = this._renderNode.renderer.handler.createArrayBuffer(
                new Float32Array(this._verticesHigh),
                3,
                this._verticesHigh.length / 3
            );
            this._verticesLowBuffer = this._renderNode.renderer.handler.createArrayBuffer(
                new Float32Array(this._verticesLow),
                3,
                this._verticesLow.length / 3
            );
            this._indexBuffer = this._renderNode.renderer.handler.createElementArrayBuffer(
                new Uint32Array(this._indexes),
                1,
                this._indexes.length
            );
        }
    }

    addEdge3v(p2, p3) {
        let length = this._path.length;

        if (length === 0) {
            this._path.push([p2.clone(), p3.clone()]);
        } else {
            let p0 = this._path[length - 1][0],
                p1 = this._path[length - 1][1];

            this._path.push([p2.clone(), p3.clone()]);

            let vHigh = this._verticesHigh,
                vLow = this._verticesLow;

            let gs = this._gridSize,
                gs1 = gs + 1;

            let p = new Vec3();

            let last = this._verticesHigh.length / 3,
                ind = last;

            let d = Math.abs(p0.sub(p1).normal().dot(p2.sub(p0).normal()));

            for (let i = 0; i < gs1; i++) {
                let di = i / gs;
                let p02 = p0.lerp(p2, di),
                    p13 = p1.lerp(p3, di);

                for (let j = 0; j < gs1; j++) {
                    let dj = j / gs;
                    let p01 = p0.lerp(p1, dj),
                        p23 = p2.lerp(p3, dj);

                    if (d !== 1.0) {
                        new Line3(p02, p13).intersects(new Line3(p01, p23), p);
                    } else {
                        p = p23;
                    }

                    ind = last + i * gs1 + j;

                    Vec3.doubleToTwoFloats(p, _tempHigh, _tempLow);

                    let ind3 = ind * 3;

                    vHigh[ind3] = _tempHigh.x;
                    vHigh[ind3 + 1] = _tempHigh.y;
                    vHigh[ind3 + 2] = _tempHigh.z;

                    vLow[ind3] = _tempLow.x;
                    vLow[ind3 + 1] = _tempLow.y;
                    vLow[ind3 + 2] = _tempLow.z;

                    if (i < gs) {
                        this._indexes.push(ind, ind + gs1);
                    }
                }

                if (i < gs) {
                    this._indexes.push(ind + gs1, ind + 1);
                }
            }

            this._createBuffers();
        }
    }

    setEdge3v(p2, p3, index) {
        if (index === this._path.length) {
            this.addEdge3v(p2, p3);
            return;
        }
        if (this._path[index]) {
            this._path[index][0] = p2;
            this._path[index][1] = p3;

            if (this._path.length > 1) {
                let gs = this._gridSize,
                    gs1 = gs + 1;

                let vSize = gs1 * gs1;

                let p = new Vec3();

                let vHigh = this._verticesHigh,
                    vLow = this._verticesLow;

                if (index === this._path.length - 1) {
                    let p0 = this._path[index - 1][0],
                        p1 = this._path[index - 1][1];

                    let prev = this._verticesHigh.length / 3 - vSize,
                        ind = prev;

                    let d = Math.abs(p0.sub(p1).normal().dot(p2.sub(p0).normal()));

                    for (let i = 0; i < gs1; i++) {
                        let di = i / gs;
                        let p02 = p0.lerp(p2, di),
                            p13 = p1.lerp(p3, di);

                        for (let j = 0; j < gs1; j++) {
                            let dj = j / gs;
                            let p01 = p0.lerp(p1, dj),
                                p23 = p2.lerp(p3, dj);

                            if (d !== 1.0) {
                                new Line3(p02, p13).intersects(new Line3(p01, p23), p);
                            } else {
                                p = p23;
                            }

                            ind = prev + i * gs1 + j;

                            Vec3.doubleToTwoFloats(p, _tempHigh, _tempLow);

                            let ind3 = ind * 3;

                            vHigh[ind3] = _tempHigh.x;
                            vHigh[ind3 + 1] = _tempHigh.y;
                            vHigh[ind3 + 2] = _tempHigh.z;

                            vLow[ind3] = _tempLow.x;
                            vLow[ind3 + 1] = _tempLow.y;
                            vLow[ind3 + 2] = _tempLow.z;
                        }
                    }
                } else if (index === 0) {
                    let ind = 0;

                    let p0 = p2,
                        p1 = p3;

                    p2 = this._path[1][0];
                    p3 = this._path[1][1];

                    for (let i = 0; i < gs1; i++) {
                        let di = i / gs;
                        let p02 = p0.lerp(p2, di),
                            p13 = p1.lerp(p3, di);

                        for (let j = 0; j < gs1; j++) {
                            let dj = j / gs;
                            let p01 = p0.lerp(p1, dj),
                                p23 = p2.lerp(p3, dj);

                            new Line3(p02, p13).intersects(new Line3(p01, p23), p);

                            ind = i * gs1 + j;

                            Vec3.doubleToTwoFloats(p, _tempHigh, _tempLow);

                            let ind3 = ind * 3;

                            vHigh[ind3] = _tempHigh.x;
                            vHigh[ind3 + 1] = _tempHigh.y;
                            vHigh[ind3 + 2] = _tempHigh.z;

                            vLow[ind3] = _tempLow.x;
                            vLow[ind3 + 1] = _tempLow.y;
                            vLow[ind3 + 2] = _tempLow.z;
                        }
                    }
                } else if (index > 0 && index < this._path.length) {
                    let p0 = this._path[index - 1][0],
                        p1 = this._path[index - 1][1];

                    let p4 = this._path[index + 1][0],
                        p5 = this._path[index + 1][1];

                    let next = index * vSize,
                        prev = (index - 1) * vSize,
                        ind = prev;

                    for (let i = 0; i < gs1; i++) {
                        let di = i / gs;
                        let p02 = p0.lerp(p2, di),
                            p35 = p3.lerp(p5, di),
                            p24 = p2.lerp(p4, di),
                            p13 = p1.lerp(p3, di);

                        for (let j = 0; j < gs1; j++) {
                            let dj = j / gs;
                            let p01 = p0.lerp(p1, dj),
                                p23 = p2.lerp(p3, dj);

                            // prev
                            new Line3(p02, p13).intersects(new Line3(p01, p23), p);

                            let ij = i * gs1 + j;

                            ind = prev + ij;

                            Vec3.doubleToTwoFloats(p, _tempHigh, _tempLow);

                            let ind3 = ind * 3;

                            vHigh[ind3] = _tempHigh.x;
                            vHigh[ind3 + 1] = _tempHigh.y;
                            vHigh[ind3 + 2] = _tempHigh.z;

                            vLow[ind3] = _tempLow.x;
                            vLow[ind3 + 1] = _tempLow.y;
                            vLow[ind3 + 2] = _tempLow.z;

                            // next
                            let p45 = p4.lerp(p5, dj);

                            p23 = p2.lerp(p3, dj);

                            new Line3(p24, p35).intersects(new Line3(p23, p45), p);

                            ind = next + ij;

                            Vec3.doubleToTwoFloats(p, _tempHigh, _tempLow);

                            ind3 = ind * 3;

                            vHigh[ind3] = _tempHigh.x;
                            vHigh[ind3 + 1] = _tempHigh.y;
                            vHigh[ind3 + 2] = _tempHigh.z;

                            vLow[ind3] = _tempLow.x;
                            vLow[ind3 + 1] = _tempLow.y;
                            vLow[ind3 + 2] = _tempLow.z;
                        }
                    }
                }

                this._createBuffers();
            }
        } else {
            console.warn(`strip index ${index} is out of range`);
        }
    }

    removeEdge(index) {
        this._path.splice(index, 1);
        this.setPath([].concat(this._path));
    }

    setGridSize(gridSize) {
        this._gridSize = gridSize;
        this.setPath([].concat(this._path));
    }

    getPath() {
        return this._path;
    }

    setPath(path) {
        this._verticesHigh = [];
        this._verticesLow = [];
        this._indexes = [];
        this._path = [];

        for (let i = 0; i < path.length; i++) {
            let p0 = path[i][0],
                p1 = path[i][1];

            if (p0 instanceof Array) {
                p0 = new Vec3(p0[0], p0[1], p0[2]);
            }

            if (p1 instanceof Array) {
                p1 = new Vec3(p1[0], p1[1], p1[2]);
            }

            this.addEdge3v(p0, p1);
        }
    }

    insertEdge3v(p0, p1, index) {
        if (index < this._path.length) {
            let p = [].concat(this._path);
            p.splice(index, 0, [p0, p1]);
            this.setPath(p);
        } else if (index === this._path.length) {
            this.addEdge3v(p0, p1);
        }
    }
}

export { Strip };