Reference Source

src/viewer/scene/math/isTriangleMeshSolid.js

/**
 * Uses edge adjacency counts to identify if the given triangle mesh can be rendered with backface culling enabled.
 *
 * If all edges are connected to exactly two triangles, then the mesh will likely be a closed solid, and we can safely
 * render it with backface culling enabled.
 *
 * Otherwise, the mesh is a surface, and we must render it with backface culling disabled.
 *
 * @private
 */
const isTriangleMeshSolid = (indices, positions) => {

    const vertexIndexMapping = [];
    let edges = [];

    function compareIndexPositions(a, b) {
        let posA, posB;

        for (let i = 0; i < 3; i++) {
            posA = positions [a * 3 + i];
            posB = positions [b * 3 + i];

            if (posA !== posB) {
                return posB - posA;
            }
        }

        return 0;
    };

    // Group together indices corresponding to same position coordinates
    let newIndices = indices.slice().sort(compareIndexPositions);

    // Calculate the mapping:
    // - from original index in indices array
    // - to indices-for-unique-positions
    let uniqueVertexIndex = null;

    for (let i = 0, len = newIndices.length; i < len; i++) {
        if (i === 0 || 0 !== compareIndexPositions(
            newIndices[i],
            newIndices[i - 1],
        )) {
            // different position
            uniqueVertexIndex = newIndices [i];
        }

        vertexIndexMapping [
            newIndices[i]
            ] = uniqueVertexIndex;
    }

    // Generate the list of edges
    for (let i = 0, len = indices.length; i < len; i += 3) {

        const a = vertexIndexMapping[indices[i]];
        const b = vertexIndexMapping[indices[i + 1]];
        const c = vertexIndexMapping[indices[i + 2]];

        let a2 = a;
        let b2 = b;
        let c2 = c;

        if (a > b && a > c) {
            if (b > c) {
                a2 = a;
                b2 = b;
                c2 = c;
            } else {
                a2 = a;
                b2 = c;
                c2 = b;
            }
        } else if (b > a && b > c) {
            if (a > c) {
                a2 = b;
                b2 = a;
                c2 = c;
            } else {
                a2 = b;
                b2 = c;
                c2 = a;
            }
        } else if (c > a && c > b) {
            if (a > b) {
                a2 = c;
                b2 = a;
                c2 = b;
            } else {
                a2 = c;
                b2 = b;
                c2 = a;
            }
        }

        edges[i + 0] = [
            a2, b2
        ];
        edges[i + 1] = [
            b2, c2
        ];

        if (a2 > c2) {
            const temp = c2;
            c2 = a2;
            a2 = temp;
        }

        edges[i + 2] = [
            c2, a2
        ];
    }

    // Group semantically equivalent edgdes together
    function compareEdges(e1, e2) {
        let a, b;

        for (let i = 0; i < 2; i++) {
            a = e1[i];
            b = e2[i];

            if (b !== a) {
                return b - a;
            }
        }

        return 0;
    }

    edges = edges.slice(0, indices.length);

    edges.sort(compareEdges);

    // Make sure each edge is used exactly twice
    let sameEdgeCount = 0;

    for (let i = 0; i < edges.length; i++) {
        if (i === 0 || 0 !== compareEdges(
            edges[i], edges[i - 1]
        )) {
            // different edge
            if (0 !== i && sameEdgeCount !== 2) {
                return false;
            }

            sameEdgeCount = 1;
        } else {
            // same edge
            sameEdgeCount++;
        }
    }

    if (edges.length > 0 && sameEdgeCount !== 2) {
        return false;
    }

    // Each edge is used exactly twice, this is a
    // watertight surface and hence a solid geometry.
    return true;
};

export {isTriangleMeshSolid};