import { Vector3 } from "../Maths/math.vector.pure.js";
import { VertexBuffer } from "../Buffers/buffer.pure.js";
import { SubMesh } from "../Meshes/subMesh.js";
import { Mesh } from "../Meshes/mesh.pure.js";
import { AsyncLoop } from "../Misc/tools.pure.js";
import { Epsilon } from "../Maths/math.constants.js";
import { RegisterMeshSimplificationSceneComponent } from "./meshSimplificationSceneComponent.pure.js";
export { SimplificationSettings, SimplificationType } from "./meshSimplification.common.js";
/**
 * Queue used to order the simplification tasks
 * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/simplifyingMeshes
 */
export class SimplificationQueue {
    /**
     * Creates a new queue
     */
    constructor() {
        this._meshSimplifierMap = new WeakMap();
        RegisterMeshSimplificationSceneComponent(SimplificationQueue);
        this.running = false;
        this._simplificationArray = [];
    }
    /**
     * Adds a new simplification task
     * @param task defines a task to add
     */
    addTask(task) {
        this._simplificationArray.push(task);
    }
    /**
     * Execute next task
     */
    executeNext() {
        const task = this._simplificationArray.pop();
        if (task) {
            this.running = true;
            this.runSimplification(task);
        }
        else {
            this.running = false;
        }
    }
    /**
     * Execute a simplification task
     * @param task defines the task to run
     */
    runSimplification(task) {
        if (task.parallelProcessing) {
            //parallel simplifier
            for (const setting of task.settings) {
                const simplifier = this._getSimplifier(task);
                simplifier.simplify(setting, true, (newMesh) => {
                    if (setting.distance !== undefined) {
                        task.mesh.addLODLevel(setting.distance, newMesh);
                    }
                    newMesh.isVisible = true;
                    //check if it is the last
                    if (setting.quality === task.settings[task.settings.length - 1].quality && task.successCallback) {
                        //all done, run the success callback.
                        task.successCallback();
                    }
                    this.executeNext();
                });
            }
        }
        else {
            //single simplifier.
            const simplifier = this._getSimplifier(task);
            const runDecimation = (setting, callback) => {
                simplifier.simplify(setting, false, (newMesh) => {
                    if (setting.distance !== undefined) {
                        task.mesh.addLODLevel(setting.distance, newMesh);
                    }
                    newMesh.isVisible = true;
                    //run the next quality level
                    callback();
                });
            };
            AsyncLoop.Run(task.settings.length, (loop) => {
                runDecimation(task.settings[loop.index], () => {
                    loop.executeNext();
                });
            }, () => {
                //execution ended, run the success callback.
                if (task.successCallback) {
                    task.successCallback();
                }
                this.executeNext();
            });
        }
    }
    _getSimplifier(task) {
        switch (task.simplificationType) {
            case 0 /* SimplificationType.QUADRATIC */:
            default:
                if (!this._meshSimplifierMap.has(task.mesh)) {
                    this._meshSimplifierMap.set(task.mesh, new QuadraticErrorSimplification(task.mesh));
                }
                return this._meshSimplifierMap.get(task.mesh);
        }
    }
}
class DecimationTriangle {
    constructor(_vertices) {
        this._vertices = _vertices;
        this.error = new Array(4);
        this.deleted = false;
        this.isDirty = false;
        this.deletePending = false;
        this.borderFactor = 0;
    }
}
class DecimationVertex {
    constructor(position, id) {
        this.position = position;
        this.id = id;
        this.isBorder = true;
        this.q = new QuadraticMatrix();
        this.triangleCount = 0;
        this.triangleStart = 0;
        this.originalOffsets = [];
    }
    /** @internal */
    updatePosition(newPosition) {
        this.position.copyFrom(newPosition);
    }
}
class QuadraticMatrix {
    constructor(data) {
        this.data = new Array(10);
        for (let i = 0; i < 10; ++i) {
            if (data && data[i]) {
                this.data[i] = data[i];
            }
            else {
                this.data[i] = 0;
            }
        }
    }
    /** @internal */
    det(a11, a12, a13, a21, a22, a23, a31, a32, a33) {
        const det = this.data[a11] * this.data[a22] * this.data[a33] +
            this.data[a13] * this.data[a21] * this.data[a32] +
            this.data[a12] * this.data[a23] * this.data[a31] -
            this.data[a13] * this.data[a22] * this.data[a31] -
            this.data[a11] * this.data[a23] * this.data[a32] -
            this.data[a12] * this.data[a21] * this.data[a33];
        return det;
    }
    /** @internal */
    addInPlace(matrix) {
        for (let i = 0; i < 10; ++i) {
            this.data[i] += matrix.data[i];
        }
    }
    /** @internal */
    addArrayInPlace(data) {
        for (let i = 0; i < 10; ++i) {
            this.data[i] += data[i];
        }
    }
    /** @internal */
    add(matrix) {
        const m = new QuadraticMatrix();
        for (let i = 0; i < 10; ++i) {
            m.data[i] = this.data[i] + matrix.data[i];
        }
        return m;
    }
    /** @internal */
    static FromData(a, b, c, d) {
        return new QuadraticMatrix(QuadraticMatrix.DataFromNumbers(a, b, c, d));
    }
    //returning an array to avoid garbage collection
    static DataFromNumbers(a, b, c, d) {
        return [a * a, a * b, a * c, a * d, b * b, b * c, b * d, c * c, c * d, d * d];
    }
}
class Reference {
    constructor(vertexId, triangleId) {
        this.vertexId = vertexId;
        this.triangleId = triangleId;
    }
}
/**
 * An implementation of the Quadratic Error simplification algorithm.
 * Original paper : http://www1.cs.columbia.edu/~cs4162/html05s/garland97.pdf
 * Ported mostly from QSlim and http://voxels.blogspot.de/2014/05/quadric-mesh-simplification-with-source.html to babylon JS
 * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/simplifyingMeshes
 */
export class QuadraticErrorSimplification {
    /**
     * Creates a new QuadraticErrorSimplification
     * @param _mesh defines the target mesh
     */
    constructor(_mesh) {
        this._mesh = _mesh;
        /** Gets or sets the number pf sync iterations */
        this.syncIterations = 5000;
        this.aggressiveness = 7;
        this.decimationIterations = 100;
        this.boundingBoxEpsilon = Epsilon;
    }
    /**
     * Simplification of a given mesh according to the given settings.
     * Since this requires computation, it is assumed that the function runs async.
     * @param settings The settings of the simplification, including quality and distance
     * @param parallelProcessing defines if should process submeshes parallelly.
     * @param successCallback A callback that will be called after the mesh was simplified.
     */
    simplify(settings, parallelProcessing, successCallback) {
        this._initDecimatedMesh();
        //iterating through the submeshes array, one after the other.
        if (!parallelProcessing) {
            AsyncLoop.Run(this._mesh.subMeshes?.length ?? 0, (loop) => {
                this._initWithMesh(loop.index, () => {
                    this._runDecimation(settings, loop.index, false, () => {
                        loop.executeNext();
                    });
                }, parallelProcessing, settings.optimizeMesh);
            }, () => {
                setTimeout(() => {
                    successCallback(this._reconstructedMesh);
                }, 0);
            });
        }
        else {
            if (this._mesh.subMeshes) {
                for (let i = 0; i < this._mesh.subMeshes.length; i++) {
                    this._initWithMesh(i, () => {
                        this._runDecimation(settings, i, true);
                    }, parallelProcessing, settings.optimizeMesh);
                }
                successCallback(this._reconstructedMesh);
            }
        }
    }
    _runDecimation(settings, submeshIndex, parallelProcessing, successCallback) {
        const targetCount = ~~(this._triangles.length * settings.quality);
        let deletedTriangles = 0;
        const triangleCount = this._triangles.length;
        const iterationFunction = (iteration, callback) => {
            const processHandle = () => {
                if (iteration % 5 === 0) {
                    this._updateMesh(iteration === 0);
                }
                for (let i = 0; i < this._triangles.length; ++i) {
                    this._triangles[i].isDirty = false;
                }
                const threshold = 0.000000001 * Math.pow(iteration + 3, this.aggressiveness);
                const trianglesIterator = (i) => {
                    const tIdx = ~~((this._triangles.length / 2 + i) % this._triangles.length);
                    const t = this._triangles[tIdx];
                    if (!t) {
                        return;
                    }
                    if (t.error[3] > threshold || t.deleted || t.isDirty) {
                        return;
                    }
                    for (let j = 0; j < 3; ++j) {
                        if (t.error[j] < threshold) {
                            const deleted0 = [];
                            const deleted1 = [];
                            const v0 = t._vertices[j];
                            const v1 = t._vertices[(j + 1) % 3];
                            if (v0.isBorder || v1.isBorder) {
                                continue;
                            }
                            const p = Vector3.Zero();
                            // var n = Vector3.Zero();
                            // var uv = Vector2.Zero();
                            // var color = new Color4(0, 0, 0, 1);
                            this._calculateError(v0, v1, p);
                            const delTr = [];
                            if (this._isFlipped(v0, v1, p, deleted0, delTr)) {
                                continue;
                            }
                            if (this._isFlipped(v1, v0, p, deleted1, delTr)) {
                                continue;
                            }
                            if (deleted0.indexOf(true) < 0 || deleted1.indexOf(true) < 0) {
                                continue;
                            }
                            const uniqueArray = [];
                            for (const deletedT of delTr) {
                                if (uniqueArray.indexOf(deletedT) === -1) {
                                    deletedT.deletePending = true;
                                    uniqueArray.push(deletedT);
                                }
                            }
                            if (uniqueArray.length % 2 !== 0) {
                                continue;
                            }
                            v0.q = v1.q.add(v0.q);
                            v0.updatePosition(p);
                            const tStart = this._references.length;
                            deletedTriangles = this._updateTriangles(v0, v0, deleted0, deletedTriangles);
                            deletedTriangles = this._updateTriangles(v0, v1, deleted1, deletedTriangles);
                            const tCount = this._references.length - tStart;
                            if (tCount <= v0.triangleCount) {
                                if (tCount) {
                                    for (let c = 0; c < tCount; c++) {
                                        this._references[v0.triangleStart + c] = this._references[tStart + c];
                                    }
                                }
                            }
                            else {
                                v0.triangleStart = tStart;
                            }
                            v0.triangleCount = tCount;
                            break;
                        }
                    }
                };
                AsyncLoop.SyncAsyncForLoop(this._triangles.length, this.syncIterations, trianglesIterator, callback || (() => { }), () => {
                    return triangleCount - deletedTriangles <= targetCount;
                }, 0, parallelProcessing);
            };
            if (parallelProcessing) {
                processHandle();
            }
            else {
                setTimeout(() => {
                    processHandle();
                }, 0);
            }
        };
        if (parallelProcessing) {
            for (let i = 0; i < this.decimationIterations; i++) {
                iterationFunction(i);
            }
            this._reconstructMesh(submeshIndex);
            successCallback && successCallback();
        }
        else {
            AsyncLoop.Run(this.decimationIterations, (loop) => {
                if (triangleCount - deletedTriangles <= targetCount) {
                    loop.breakLoop();
                }
                else {
                    iterationFunction(loop.index, () => {
                        loop.executeNext();
                    });
                }
            }, () => {
                setTimeout(() => {
                    //reconstruct this part of the mesh
                    this._reconstructMesh(submeshIndex);
                    successCallback && successCallback();
                }, 0);
            });
        }
    }
    _initWithMesh(submeshIndex, callback, parallelProcessing, optimizeMesh) {
        this._vertices = [];
        this._triangles = [];
        const positionData = this._mesh.getVerticesData(VertexBuffer.PositionKind);
        const indices = this._mesh.getIndices();
        const submesh = this._mesh.subMeshes[submeshIndex];
        const findInVertices = (positionToSearch) => {
            if (optimizeMesh) {
                for (let ii = 0; ii < this._vertices.length; ++ii) {
                    if (this._vertices[ii].position.equalsWithEpsilon(positionToSearch, 0.0001)) {
                        return this._vertices[ii];
                    }
                }
            }
            return null;
        };
        const vertexReferences = [];
        const vertexInit = (i) => {
            if (!positionData) {
                return;
            }
            const offset = i + submesh.verticesStart;
            const position = Vector3.FromArray(positionData, offset * 3);
            const vertex = findInVertices(position) || new DecimationVertex(position, this._vertices.length);
            vertex.originalOffsets.push(offset);
            if (vertex.id === this._vertices.length) {
                this._vertices.push(vertex);
            }
            vertexReferences.push(vertex.id);
        };
        //var totalVertices = mesh.getTotalVertices();
        const totalVertices = submesh.verticesCount;
        AsyncLoop.SyncAsyncForLoop(totalVertices, (this.syncIterations / 4) >> 0, vertexInit, () => {
            const indicesInit = (i) => {
                if (!indices) {
                    return;
                }
                const offset = submesh.indexStart / 3 + i;
                const pos = offset * 3;
                const i0 = indices[pos + 0];
                const i1 = indices[pos + 1];
                const i2 = indices[pos + 2];
                const v0 = this._vertices[vertexReferences[i0 - submesh.verticesStart]];
                const v1 = this._vertices[vertexReferences[i1 - submesh.verticesStart]];
                const v2 = this._vertices[vertexReferences[i2 - submesh.verticesStart]];
                const triangle = new DecimationTriangle([v0, v1, v2]);
                triangle.originalOffset = pos;
                this._triangles.push(triangle);
            };
            AsyncLoop.SyncAsyncForLoop(submesh.indexCount / 3, this.syncIterations, indicesInit, () => {
                this._init(callback, parallelProcessing);
            }, undefined, 0, parallelProcessing);
        }, undefined, 0, parallelProcessing);
    }
    _init(callback, parallelProcessing) {
        const triangleInit1 = (i) => {
            const t = this._triangles[i];
            t.normal = Vector3.Cross(t._vertices[1].position.subtract(t._vertices[0].position), t._vertices[2].position.subtract(t._vertices[0].position)).normalize();
            for (let j = 0; j < 3; j++) {
                t._vertices[j].q.addArrayInPlace(QuadraticMatrix.DataFromNumbers(t.normal.x, t.normal.y, t.normal.z, -Vector3.Dot(t.normal, t._vertices[0].position)));
            }
        };
        AsyncLoop.SyncAsyncForLoop(this._triangles.length, this.syncIterations, triangleInit1, () => {
            const triangleInit2 = (i) => {
                const t = this._triangles[i];
                for (let j = 0; j < 3; ++j) {
                    t.error[j] = this._calculateError(t._vertices[j], t._vertices[(j + 1) % 3]);
                }
                t.error[3] = Math.min(t.error[0], t.error[1], t.error[2]);
            };
            AsyncLoop.SyncAsyncForLoop(this._triangles.length, this.syncIterations, triangleInit2, () => {
                callback();
            }, undefined, 0, parallelProcessing);
        }, undefined, 0, parallelProcessing);
    }
    _reconstructMesh(submeshIndex) {
        const newTriangles = [];
        let i;
        for (i = 0; i < this._vertices.length; ++i) {
            this._vertices[i].triangleCount = 0;
        }
        let t;
        let j;
        for (i = 0; i < this._triangles.length; ++i) {
            if (!this._triangles[i].deleted) {
                t = this._triangles[i];
                for (j = 0; j < 3; ++j) {
                    t._vertices[j].triangleCount = 1;
                }
                newTriangles.push(t);
            }
        }
        const newPositionData = (this._reconstructedMesh.getVerticesData(VertexBuffer.PositionKind) || []);
        const newNormalData = (this._reconstructedMesh.getVerticesData(VertexBuffer.NormalKind) || []);
        const newUVsData = (this._reconstructedMesh.getVerticesData(VertexBuffer.UVKind) || []);
        const newColorsData = (this._reconstructedMesh.getVerticesData(VertexBuffer.ColorKind) || []);
        const normalData = this._mesh.getVerticesData(VertexBuffer.NormalKind);
        const uvs = this._mesh.getVerticesData(VertexBuffer.UVKind);
        const colorsData = this._mesh.getVerticesData(VertexBuffer.ColorKind);
        let vertexCount = 0;
        for (i = 0; i < this._vertices.length; ++i) {
            const vertex = this._vertices[i];
            vertex.id = vertexCount;
            if (vertex.triangleCount) {
                for (const originalOffset of vertex.originalOffsets) {
                    newPositionData.push(vertex.position.x);
                    newPositionData.push(vertex.position.y);
                    newPositionData.push(vertex.position.z);
                    if (normalData && normalData.length) {
                        newNormalData.push(normalData[originalOffset * 3]);
                        newNormalData.push(normalData[originalOffset * 3 + 1]);
                        newNormalData.push(normalData[originalOffset * 3 + 2]);
                    }
                    if (uvs && uvs.length) {
                        newUVsData.push(uvs[originalOffset * 2]);
                        newUVsData.push(uvs[originalOffset * 2 + 1]);
                    }
                    if (colorsData && colorsData.length) {
                        newColorsData.push(colorsData[originalOffset * 4]);
                        newColorsData.push(colorsData[originalOffset * 4 + 1]);
                        newColorsData.push(colorsData[originalOffset * 4 + 2]);
                        newColorsData.push(colorsData[originalOffset * 4 + 3]);
                    }
                    ++vertexCount;
                }
            }
        }
        const startingIndex = this._reconstructedMesh.getTotalIndices();
        const startingVertex = this._reconstructedMesh.getTotalVertices();
        const submeshesArray = this._reconstructedMesh.subMeshes;
        this._reconstructedMesh.subMeshes = [];
        const newIndicesArray = this._reconstructedMesh.getIndices(); //[];
        const originalIndices = this._mesh.getIndices();
        for (i = 0; i < newTriangles.length; ++i) {
            t = newTriangles[i]; //now get the new referencing point for each vertex
            for (let idx = 0; idx < 3; ++idx) {
                const id = originalIndices[t.originalOffset + idx];
                let offset = t._vertices[idx].originalOffsets.indexOf(id);
                if (offset < 0) {
                    offset = 0;
                }
                newIndicesArray.push(t._vertices[idx].id + offset + startingVertex);
            }
        }
        //overwriting the old vertex buffers and indices.
        this._reconstructedMesh.setIndices(newIndicesArray);
        this._reconstructedMesh.setVerticesData(VertexBuffer.PositionKind, newPositionData);
        if (newNormalData.length > 0) {
            this._reconstructedMesh.setVerticesData(VertexBuffer.NormalKind, newNormalData);
        }
        if (newUVsData.length > 0) {
            this._reconstructedMesh.setVerticesData(VertexBuffer.UVKind, newUVsData);
        }
        if (newColorsData.length > 0) {
            this._reconstructedMesh.setVerticesData(VertexBuffer.ColorKind, newColorsData);
        }
        //create submesh
        const originalSubmesh = this._mesh.subMeshes[submeshIndex];
        if (submeshIndex > 0) {
            this._reconstructedMesh.subMeshes = [];
            for (const submesh of submeshesArray) {
                SubMesh.AddToMesh(submesh.materialIndex, submesh.verticesStart, submesh.verticesCount, 
                /* 0, newPositionData.length/3, */ submesh.indexStart, submesh.indexCount, submesh.getMesh());
            }
            SubMesh.AddToMesh(originalSubmesh.materialIndex, startingVertex, vertexCount, 
            /* 0, newPositionData.length / 3, */ startingIndex, newTriangles.length * 3, this._reconstructedMesh);
        }
    }
    _initDecimatedMesh() {
        this._reconstructedMesh = new Mesh(this._mesh.name + "Decimated", this._mesh.getScene());
        this._reconstructedMesh.material = this._mesh.material;
        this._reconstructedMesh.parent = this._mesh.parent;
        this._reconstructedMesh.isVisible = false;
        this._reconstructedMesh.renderingGroupId = this._mesh.renderingGroupId;
        this._reconstructedMesh.isDecimatedMesh = true;
    }
    _isFlipped(vertex1, vertex2, point, deletedArray, delTr) {
        for (let i = 0; i < vertex1.triangleCount; ++i) {
            const t = this._triangles[this._references[vertex1.triangleStart + i].triangleId];
            if (t.deleted) {
                continue;
            }
            const s = this._references[vertex1.triangleStart + i].vertexId;
            const v1 = t._vertices[(s + 1) % 3];
            const v2 = t._vertices[(s + 2) % 3];
            if (v1 === vertex2 || v2 === vertex2) {
                deletedArray[i] = true;
                delTr.push(t);
                continue;
            }
            let d1 = v1.position.subtract(point);
            d1 = d1.normalize();
            let d2 = v2.position.subtract(point);
            d2 = d2.normalize();
            if (Math.abs(Vector3.Dot(d1, d2)) > 0.999) {
                return true;
            }
            const normal = Vector3.Cross(d1, d2).normalize();
            deletedArray[i] = false;
            if (Vector3.Dot(normal, t.normal) < 0.2) {
                return true;
            }
        }
        return false;
    }
    _updateTriangles(origVertex, vertex, deletedArray, deletedTriangles) {
        let newDeleted = deletedTriangles;
        for (let i = 0; i < vertex.triangleCount; ++i) {
            const ref = this._references[vertex.triangleStart + i];
            const t = this._triangles[ref.triangleId];
            if (t.deleted) {
                continue;
            }
            if (deletedArray[i] && t.deletePending) {
                t.deleted = true;
                newDeleted++;
                continue;
            }
            t._vertices[ref.vertexId] = origVertex;
            t.isDirty = true;
            t.error[0] = this._calculateError(t._vertices[0], t._vertices[1]) + t.borderFactor / 2;
            t.error[1] = this._calculateError(t._vertices[1], t._vertices[2]) + t.borderFactor / 2;
            t.error[2] = this._calculateError(t._vertices[2], t._vertices[0]) + t.borderFactor / 2;
            t.error[3] = Math.min(t.error[0], t.error[1], t.error[2]);
            this._references.push(ref);
        }
        return newDeleted;
    }
    _identifyBorder() {
        for (let i = 0; i < this._vertices.length; ++i) {
            const vCount = [];
            const vId = [];
            const v = this._vertices[i];
            let j;
            for (j = 0; j < v.triangleCount; ++j) {
                const triangle = this._triangles[this._references[v.triangleStart + j].triangleId];
                for (let ii = 0; ii < 3; ii++) {
                    let ofs = 0;
                    const vv = triangle._vertices[ii];
                    while (ofs < vCount.length) {
                        if (vId[ofs] === vv.id) {
                            break;
                        }
                        ++ofs;
                    }
                    if (ofs === vCount.length) {
                        vCount.push(1);
                        vId.push(vv.id);
                    }
                    else {
                        vCount[ofs]++;
                    }
                }
            }
            for (j = 0; j < vCount.length; ++j) {
                if (vCount[j] === 1) {
                    this._vertices[vId[j]].isBorder = true;
                }
                else {
                    this._vertices[vId[j]].isBorder = false;
                }
            }
        }
    }
    _updateMesh(identifyBorders = false) {
        let i;
        if (!identifyBorders) {
            const newTrianglesVector = [];
            for (i = 0; i < this._triangles.length; ++i) {
                if (!this._triangles[i].deleted) {
                    newTrianglesVector.push(this._triangles[i]);
                }
            }
            this._triangles = newTrianglesVector;
        }
        for (i = 0; i < this._vertices.length; ++i) {
            this._vertices[i].triangleCount = 0;
            this._vertices[i].triangleStart = 0;
        }
        let t;
        let j;
        let v;
        for (i = 0; i < this._triangles.length; ++i) {
            t = this._triangles[i];
            for (j = 0; j < 3; ++j) {
                v = t._vertices[j];
                v.triangleCount++;
            }
        }
        let tStart = 0;
        for (i = 0; i < this._vertices.length; ++i) {
            this._vertices[i].triangleStart = tStart;
            tStart += this._vertices[i].triangleCount;
            this._vertices[i].triangleCount = 0;
        }
        const newReferences = new Array(this._triangles.length * 3);
        for (i = 0; i < this._triangles.length; ++i) {
            t = this._triangles[i];
            for (j = 0; j < 3; ++j) {
                v = t._vertices[j];
                newReferences[v.triangleStart + v.triangleCount] = new Reference(j, i);
                v.triangleCount++;
            }
        }
        this._references = newReferences;
        if (identifyBorders) {
            this._identifyBorder();
        }
    }
    _vertexError(q, point) {
        const x = point.x;
        const y = point.y;
        const z = point.z;
        return (q.data[0] * x * x +
            2 * q.data[1] * x * y +
            2 * q.data[2] * x * z +
            2 * q.data[3] * x +
            q.data[4] * y * y +
            2 * q.data[5] * y * z +
            2 * q.data[6] * y +
            q.data[7] * z * z +
            2 * q.data[8] * z +
            q.data[9]);
    }
    _calculateError(vertex1, vertex2, pointResult) {
        const q = vertex1.q.add(vertex2.q);
        const border = vertex1.isBorder && vertex2.isBorder;
        let error;
        const qDet = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
        if (qDet !== 0 && !border) {
            if (!pointResult) {
                pointResult = Vector3.Zero();
            }
            pointResult.x = (-1 / qDet) * q.det(1, 2, 3, 4, 5, 6, 5, 7, 8);
            pointResult.y = (1 / qDet) * q.det(0, 2, 3, 1, 5, 6, 2, 7, 8);
            pointResult.z = (-1 / qDet) * q.det(0, 1, 3, 1, 4, 6, 2, 5, 8);
            error = this._vertexError(q, pointResult);
        }
        else {
            const p3 = vertex1.position.add(vertex2.position).divide(new Vector3(2, 2, 2));
            //var norm3 = (vertex1.normal.add(vertex2.normal)).divide(new Vector3(2, 2, 2)).normalize();
            const error1 = this._vertexError(q, vertex1.position);
            const error2 = this._vertexError(q, vertex2.position);
            const error3 = this._vertexError(q, p3);
            error = Math.min(error1, error2, error3);
            if (error === error1) {
                if (pointResult) {
                    pointResult.copyFrom(vertex1.position);
                }
            }
            else if (error === error2) {
                if (pointResult) {
                    pointResult.copyFrom(vertex2.position);
                }
            }
            else {
                if (pointResult) {
                    pointResult.copyFrom(p3);
                }
            }
        }
        return error;
    }
}
//# sourceMappingURL=meshSimplification.js.map