/** This file must only contain pure code and pure imports */
import { Vector3 } from "../../Maths/math.vector.pure.js";
import { Mesh } from "../mesh.pure.js";
import { Buffer, VertexBuffer } from "../../Buffers/buffer.pure.js";
import { PickingInfo } from "../../Collisions/pickingInfo.js";
import { DeepCopier } from "../../Misc/deepCopier.js";
import { GreasedLineTools } from "../../Misc/greasedLineTools.js";
import { GreasedLineBaseMesh } from "./greasedLineBaseMesh.js";
/**
 * GreasedLineMesh
 * Use the GreasedLineBuilder.CreateGreasedLine function to create an instance of this class.
 */
export class GreasedLineMesh extends GreasedLineBaseMesh {
    /**
     * GreasedLineMesh
     * @param name name of the mesh
     * @param scene the scene
     * @param _options mesh options
     */
    constructor(name, scene, _options) {
        super(name, scene, _options);
        this.name = name;
        /**
         * Treshold used to pick the mesh
         */
        this.intersectionThreshold = 0.1;
        this._previousAndSide = [];
        this._nextAndCounters = [];
        if (_options.points) {
            this.addPoints(GreasedLineTools.ConvertPoints(_options.points));
        }
    }
    /**
     * "GreasedLineMesh"
     * @returns "GreasedLineMesh"
     */
    getClassName() {
        return "GreasedLineMesh";
    }
    _updateColorPointers() {
        if (this._options.colorPointers) {
            return;
        }
        let colorPointer = 0;
        this._colorPointers = [];
        for (const p of this._points) {
            for (let jj = 0; jj < p.length; jj += 3) {
                this._colorPointers.push(colorPointer);
                this._colorPointers.push(colorPointer++);
            }
        }
    }
    _updateWidths() {
        // intentionally left blank
    }
    _setPoints(points) {
        this._points = points;
        this._options.points = points;
        this._initGreasedLine();
        let indiceOffset = 0;
        let vertexPositionsLen = 0, indicesLength = 0, uvLength = 0, previousAndSideLength = 0, maxLineLength = 0;
        for (const p of points) {
            maxLineLength = Math.max(p.length, maxLineLength);
            vertexPositionsLen += p.length * 2;
            indicesLength += (p.length - 3) * 2;
            uvLength += (p.length * 4) / 3;
            previousAndSideLength += (p.length * 8) / 3;
        }
        const buffer = new ArrayBuffer(vertexPositionsLen * 4 + indicesLength * (vertexPositionsLen > 65535 ? 4 : 2) + uvLength * 4 + previousAndSideLength * 4 * 2);
        const tempBuffer = new ArrayBuffer((maxLineLength / 3 + maxLineLength * 4) * 4);
        let byteOffset = 0;
        const vertexPositionsArr = new Float32Array(buffer, byteOffset, vertexPositionsLen);
        byteOffset += vertexPositionsArr.byteLength;
        const indicesArr = vertexPositionsLen > 65535 ? new Uint32Array(buffer, byteOffset, indicesLength) : new Uint16Array(buffer, byteOffset, indicesLength);
        byteOffset += indicesArr.byteLength;
        const uvArr = new Float32Array(buffer, byteOffset, uvLength);
        byteOffset += uvArr.byteLength;
        const previousAndSide = new Float32Array(buffer, byteOffset, previousAndSideLength);
        byteOffset += previousAndSide.byteLength;
        // it's the same length here
        const nextAndCounters = new Float32Array(buffer, byteOffset, previousAndSideLength);
        let vertexPositionsOffset = 0, indicesOffset = 0, uvOffset = 0, previousAndSideOffset = 0, nextAndCountersOffset = 0;
        for (const p of points) {
            const lengthArray = GreasedLineTools.GetLineLengthArray(p, tempBuffer);
            const totalLength = lengthArray[lengthArray.length - 1];
            for (let j = 0, jj = 0; jj < p.length; j++, jj += 3) {
                const baseOffset = vertexPositionsOffset + jj * 2;
                vertexPositionsArr[baseOffset + 0] = p[jj + 0];
                vertexPositionsArr[baseOffset + 1] = p[jj + 1];
                vertexPositionsArr[baseOffset + 2] = p[jj + 2];
                vertexPositionsArr[baseOffset + 3] = p[jj + 0];
                vertexPositionsArr[baseOffset + 4] = p[jj + 1];
                vertexPositionsArr[baseOffset + 5] = p[jj + 2];
                if (jj < p.length - 3) {
                    const n = j * 2 + indiceOffset;
                    const baseIndicesOffset = indicesOffset + jj * 2;
                    indicesArr[baseIndicesOffset + 0] = n;
                    indicesArr[baseIndicesOffset + 1] = n + 1;
                    indicesArr[baseIndicesOffset + 2] = n + 2;
                    indicesArr[baseIndicesOffset + 3] = n + 2;
                    indicesArr[baseIndicesOffset + 4] = n + 1;
                    indicesArr[baseIndicesOffset + 5] = n + 3;
                }
            }
            indiceOffset += (p.length / 3) * 2;
            const currVertexPositionsOffsetLength = p.length * 2;
            const positions = vertexPositionsArr.subarray(vertexPositionsOffset, vertexPositionsOffset + currVertexPositionsOffsetLength);
            vertexPositionsOffset += currVertexPositionsOffsetLength;
            indicesOffset += (p.length - 3) * 2;
            let byteOffset = lengthArray.byteLength;
            const previous = new Float32Array(tempBuffer, byteOffset, positions.length);
            byteOffset += previous.byteLength;
            const next = new Float32Array(tempBuffer, byteOffset, positions.length);
            const l = positions.length / 6;
            let v;
            if (GreasedLineMesh._CompareV3(0, l - 1, positions)) {
                v = positions.subarray((l - 2) * 6, (l - 1) * 6);
            }
            else {
                v = positions.subarray(0, 6);
            }
            previous.set(v);
            previous.set(positions.subarray(0, positions.length - 6), 6);
            next.set(positions.subarray(6));
            if (GreasedLineMesh._CompareV3(l - 1, 0, positions)) {
                v = positions.subarray(6, 12);
            }
            else {
                v = positions.subarray((l - 1) * 6, l * 6);
            }
            next.set(v, next.length - 6);
            for (let i = 0, sidesLength = positions.length / 3; i < sidesLength; i++) {
                previousAndSide[previousAndSideOffset++] = previous[i * 3];
                previousAndSide[previousAndSideOffset++] = previous[i * 3 + 1];
                previousAndSide[previousAndSideOffset++] = previous[i * 3 + 2];
                // side[i] = i % 2 ? -1 : 1;
                // side[i] = 1 - ((i & 1) << 1);
                previousAndSide[previousAndSideOffset++] = 1 - ((i & 1) << 1);
                nextAndCounters[nextAndCountersOffset++] = next[i * 3];
                nextAndCounters[nextAndCountersOffset++] = next[i * 3 + 1];
                nextAndCounters[nextAndCountersOffset++] = next[i * 3 + 2];
                // counters[i] = lengthArray[i >> 1] / totalLength;
                nextAndCounters[nextAndCountersOffset++] = lengthArray[i >> 1] / totalLength;
            }
            if (this._options.uvs) {
                for (let i = 0; i < this._options.uvs.length; i++) {
                    uvArr[uvOffset++] = this._options.uvs[i];
                }
            }
            else {
                for (let j = 0; j < l; j++) {
                    // uvs
                    const lengthRatio = lengthArray[j] / totalLength;
                    const uvOffsetBase = uvOffset + j * 4;
                    uvArr[uvOffsetBase + 0] = lengthRatio;
                    uvArr[uvOffsetBase + 1] = 0;
                    uvArr[uvOffsetBase + 2] = lengthRatio;
                    uvArr[uvOffsetBase + 3] = 1;
                }
            }
        }
        this._vertexPositions = vertexPositionsArr;
        this._indices = indicesArr;
        this._uvs = uvArr;
        this._previousAndSide = previousAndSide;
        this._nextAndCounters = nextAndCounters;
        if (!this._lazy) {
            if (!this._options.colorPointers) {
                this._updateColorPointers();
            }
            this._createVertexBuffers();
            !this.doNotSyncBoundingInfo && this.refreshBoundingInfo();
        }
    }
    /**
     * Clones the GreasedLineMesh.
     * @param name new line name
     * @param newParent new parent node
     * @returns cloned line
     */
    clone(name = `${this.name}-cloned`, newParent) {
        const lineOptions = this._createLineOptions();
        const deepCopiedLineOptions = {};
        DeepCopier.DeepCopy(lineOptions, deepCopiedLineOptions, ["instance"], undefined, true);
        const cloned = new GreasedLineMesh(name, this._scene, deepCopiedLineOptions);
        if (newParent) {
            cloned.parent = newParent;
        }
        cloned.material = this.material;
        return cloned;
    }
    /**
     * Serializes this GreasedLineMesh
     * @param serializationObject object to write serialization to
     */
    serialize(serializationObject) {
        super.serialize(serializationObject);
        serializationObject.type = this.getClassName();
        serializationObject.lineOptions = this._createLineOptions();
    }
    /**
     * Parses a serialized GreasedLineMesh
     * @param parsedMesh the serialized GreasedLineMesh
     * @param scene the scene to create the GreasedLineMesh in
     * @returns the created GreasedLineMesh
     */
    static Parse(parsedMesh, scene) {
        const lineOptions = parsedMesh.lineOptions;
        const name = parsedMesh.name;
        const result = new GreasedLineMesh(name, scene, lineOptions);
        return result;
    }
    _initGreasedLine() {
        super._initGreasedLine();
        this._previousAndSide = [];
        this._nextAndCounters = [];
    }
    /**
     * Checks whether a ray is intersecting this GreasedLineMesh
     * @param ray ray to check the intersection of this mesh with
     * @param fastCheck not supported
     * @param trianglePredicate not supported
     * @param onlyBoundingInfo defines a boolean indicating if picking should only happen using bounding info (false by default)
     * @param worldToUse not supported
     * @param skipBoundingInfo a boolean indicating if we should skip the bounding info check
     * @returns the picking info
     */
    intersects(ray, fastCheck, trianglePredicate, onlyBoundingInfo = false, worldToUse, skipBoundingInfo = false) {
        const pickingInfo = new PickingInfo();
        const intersections = this.findAllIntersections(ray, fastCheck, trianglePredicate, onlyBoundingInfo, worldToUse, skipBoundingInfo, true);
        if (intersections?.length === 1) {
            const intersection = intersections[0];
            pickingInfo.hit = true;
            pickingInfo.distance = intersection.distance;
            pickingInfo.ray = ray;
            pickingInfo.pickedMesh = this;
            pickingInfo.pickedPoint = intersection.point;
        }
        return pickingInfo;
    }
    /**
     * Gets all intersections of a ray and the line
     * @param ray Ray to check the intersection of this mesh with
     * @param _fastCheck not supported
     * @param _trianglePredicate not supported
     * @param onlyBoundingInfo defines a boolean indicating if picking should only happen using bounding info (false by default)
     * @param _worldToUse not supported
     * @param skipBoundingInfo a boolean indicating if we should skip the bounding info check
     * @param firstOnly If true, the first and only intersection is immediatelly returned if found
     * @returns intersection(s)
     */
    findAllIntersections(ray, _fastCheck, _trianglePredicate, onlyBoundingInfo = false, _worldToUse, skipBoundingInfo = false, firstOnly = false) {
        if (onlyBoundingInfo && !skipBoundingInfo && ray.intersectsSphere(this._boundingSphere, this.intersectionThreshold) === false) {
            return;
        }
        const indices = this.getIndices();
        const positions = this.getVerticesData(VertexBuffer.PositionKind);
        const widths = this._widths;
        const lineWidth = this.greasedLineMaterial?.width ?? 1;
        const intersects = [];
        if (indices && positions && widths) {
            let i, l;
            for (i = 0, l = indices.length - 1; i < l; i += 3) {
                const a = indices[i];
                const b = indices[i + 1];
                GreasedLineMesh._V_START.fromArray(positions, a * 3);
                GreasedLineMesh._V_END.fromArray(positions, b * 3);
                if (this._offsets) {
                    GreasedLineMesh._V_OFFSET_START.fromArray(this._offsets, a * 3);
                    GreasedLineMesh._V_OFFSET_END.fromArray(this._offsets, b * 3);
                    GreasedLineMesh._V_START.addInPlace(GreasedLineMesh._V_OFFSET_START);
                    GreasedLineMesh._V_END.addInPlace(GreasedLineMesh._V_OFFSET_END);
                }
                const iFloored = Math.floor(i / 3);
                const width = widths[iFloored] !== undefined ? widths[iFloored] : 1;
                const precision = (this.intersectionThreshold * (lineWidth * width)) / 2;
                const distance = ray.intersectionSegment(GreasedLineMesh._V_START, GreasedLineMesh._V_END, precision);
                if (distance !== -1) {
                    intersects.push({
                        distance: distance,
                        point: ray.direction.normalize().multiplyByFloats(distance, distance, distance).add(ray.origin),
                    });
                    if (firstOnly) {
                        return intersects;
                    }
                }
            }
        }
        return intersects;
    }
    get _boundingSphere() {
        return this.getBoundingInfo().boundingSphere;
    }
    static _CompareV3(positionIdx1, positionIdx2, positions) {
        const arrayIdx1 = positionIdx1 * 6;
        const arrayIdx2 = positionIdx2 * 6;
        return positions[arrayIdx1] === positions[arrayIdx2] && positions[arrayIdx1 + 1] === positions[arrayIdx2 + 1] && positions[arrayIdx1 + 2] === positions[arrayIdx2 + 2];
    }
    _createVertexBuffers() {
        const vertexData = super._createVertexBuffers();
        const engine = this._scene.getEngine();
        const previousAndSideBuffer = new Buffer(engine, this._previousAndSide, false, 4);
        this.setVerticesBuffer(previousAndSideBuffer.createVertexBuffer("grl_previousAndSide", 0, 4));
        const nextAndCountersBuffer = new Buffer(engine, this._nextAndCounters, false, 4);
        this.setVerticesBuffer(nextAndCountersBuffer.createVertexBuffer("grl_nextAndCounters", 0, 4));
        const widthBuffer = new Buffer(engine, this._widths, this._updatable, 1);
        this.setVerticesBuffer(widthBuffer.createVertexBuffer("grl_widths", 0, 1));
        this._widthsBuffer = widthBuffer;
        const colorPointersBuffer = new Buffer(engine, this._colorPointers, this._updatable, 1);
        this.setVerticesBuffer(colorPointersBuffer.createVertexBuffer("grl_colorPointers", 0, 1));
        this._colorPointersBuffer = colorPointersBuffer;
        return vertexData;
    }
}
GreasedLineMesh._V_START = new Vector3();
GreasedLineMesh._V_END = new Vector3();
GreasedLineMesh._V_OFFSET_START = new Vector3();
GreasedLineMesh._V_OFFSET_END = new Vector3();
let _Registered = false;
/**
 * Register side effects for greasedLineMesh.
 * Safe to call multiple times; only the first call has an effect.
 */
export function RegisterGreasedLineMesh() {
    if (_Registered) {
        return;
    }
    _Registered = true;
    Mesh._GreasedLineMeshParser = (parsedMesh, scene) => {
        return GreasedLineMesh.Parse(parsedMesh, scene);
    };
}
//# sourceMappingURL=greasedLineMesh.pure.js.map