在 Three.js 中仅提取复杂 BufferGeometry 的外部边缘

问题描述 投票:0回答:1

我目前正在开发一个涉及使用 Three.js 进行区域创建和碰撞检测的项目,其中我的应用程序可以正确处理碰撞并生成

BufferGeometry
作为结果。我的目标是在这些区域周围直观地呈现一个框架,以清楚地描绘出它们的边界。

With bounding box, Simple shape

虽然我可以轻松地为简单的几何形状创建边界框(效果很好,如附图所示),但我遇到了更复杂的几何形状的问题。边界框不能准确反映区域的实际复杂形状。

BufferGometry, Complex shapes

为了解决这个问题,我尝试通过从顶点创建

LineSegments
来突出显示几何体的边缘。这种方法在一定程度上有效,但会导致显示太多内部线。我需要一种方法来过滤掉这些内部线条,以便只有外部边缘(复杂形状的实际轮廓)可见。

这是我的代码的相关部分,我尝试提取并显示这些边缘:

const calculateZoneCollision = (zone: PotreeVolume) => {
    const viewer: PotreeViewer = (window as any).viewer;

    if (!zone) return;

    zone.updateMatrixWorld(true);
    const allVolumes = viewer.scene.volumes.filter((volume) => volume.uuid !== zone.uuid);
    const volumeMeshes = allVolumes.map((v) => {
        return createMeshFromVolume(v);
    });

    let zoneMesh = createMeshFromVolume(zone);
    let zoneOBB = createOBBFromMesh(zoneMesh, viewer);

    let resultMesh;

    volumeMeshes.forEach((volume) => {
        volume.updateMatrixWorld(true);

        let volumeOBB = createOBBFromMesh(volume, viewer);

        if (zoneOBB.intersectsOBB(volumeOBB)) {
            console.log(`Collision detected with volume: ${volume.name}`);

            let originalCSGZone;

            if (!resultMesh) {
                originalCSGZone = CSG.fromMesh(zoneMesh);
            } else {
                originalCSGZone = CSG.fromMesh(resultMesh);
            }

            let csgZone = originalCSGZone.clone();

            let csgVolume = CSG.fromMesh(volume);
            csgZone = csgZone.subtract(csgVolume);

            const collisionInfo = calculateCollisionInfo(zoneOBB, volumeOBB);

            const details = getCollisionDetails(zoneOBB, volumeOBB, collisionInfo);
            if (details) {
                console.log(`Point of collide: `, details.collisionPoint);
                console.log(`Side of impact: `, details.side);
                console.log('Volume: ', zone, zoneMesh, zoneOBB);
            }

            const resultGeometry = CSG.toGeometry(csgZone, zoneMesh.matrix);
            const resultMaterial = new MeshBasicMaterial({ color: 0xff0000, transparent: true,      opacity: 0.001 });
            resultMesh = new Mesh(resultGeometry, resultMaterial);
            resultMesh.applyMatrix4(zoneMesh.matrixWorld);

            const vertices = [];
            const positionAttribute = resultGeometry.getAttribute('position');

            for (let i = 0; i < positionAttribute.count; i++) {
                const vertex = new Vector3();
                vertex.fromBufferAttribute(positionAttribute, i);
                vertices.push([vertex.x, vertex.y, vertex.z]);
            }

            const matrixElements = resultMesh.matrix.elements;
        } else {
            console.log(`Collision not detected`);
        }
    });

    if (resultMesh) {
        const outerEdges = extractOuterEdges(resultMesh.geometry);

        outerEdges.applyMatrix4(zoneMesh.matrixWorld);
        viewer.scene.scene.add(outerEdges);
        viewer.scene.scene.add(resultMesh);

        setTimeout(() => {
            viewer.scene.scene.remove(resultMesh);
            viewer.scene.scene.remove(outerEdges);
        }, 160);
    }
};

function extractOuterEdges(geometry) {
    const indexAttribute = geometry.index;
    const positionAttribute = geometry.getAttribute('position');
    const normalsAttribute = geometry.getAttribute('normal');
    const edgeMap = new Map();
    const edgeFaces = new Map();

    for (let i = 0; i < indexAttribute.count; i += 3) {
        let indices = [indexAttribute.getX(i), indexAttribute.getX(i + 1), indexAttribute.getX(i + 2)];
        let triangleNormals = [
            new Vector3(normalsAttribute.getX(i), normalsAttribute.getY(i), normalsAttribute.getZ(i)),
            new Vector3(normalsAttribute.getX(i + 1), normalsAttribute.getY(i + 1), normalsAttribute.getZ(i + 1)),
            new Vector3(normalsAttribute.getX(i + 2), normalsAttribute.getY(i + 2), normalsAttribute.getZ(i + 2)),
        ];

        for (let j = 0; j < 3; j++) {
            let edgeKey = [indices[j], indices[(j + 1) % 3]].sort().join('_');
            if (!edgeMap.has(edgeKey)) {
                edgeMap.set(edgeKey, 1);
                edgeFaces.set(edgeKey, [triangleNormals[j]]);
            } else {
                edgeMap.set(edgeKey, edgeMap.get(edgeKey) + 1);
                edgeFaces.get(edgeKey).push(triangleNormals[j]);
            }
        }
    }

    const outerEdgesGeometry = new BufferGeometry();
    const vertices = [];

    edgeMap.forEach((count, key) => {
        if (count === 1 || (count === 2 && !areNormalsCoPlanar(edgeFaces.get(key)[0], edgeFaces.get(key)[1]))) {
            let vertexIndices = key.split('_').map(Number);
            vertices.push(
                new Vector3().fromBufferAttribute(positionAttribute, vertexIndices[0]),
                new Vector3().fromBufferAttribute(positionAttribute, vertexIndices[1])
            );
        }
    });

    outerEdgesGeometry.setFromPoints(vertices);
    const lineMaterial = new LineBasicMaterial({ color: 0xffffff });
    const lineSegments = new LineSegments(outerEdgesGeometry, lineMaterial);

    return lineSegments;
}

function areNormalsCoPlanar(normal1, normal2) {
    return normal1.dot(normal2) > 0.995; // Stricter coplanarity check
}
javascript reactjs typescript math three.js
1个回答
0
投票
const calculateZoneCollision = (zone: PotreeVolume) => {
    const viewer: PotreeViewer = (window as any).viewer;

    if (!zone) return;

    zone.updateMatrixWorld(true);
    const allVolumes = viewer.scene.volumes.filter((volume) => volume.uuid !== zone.uuid);
    const volumeMeshes = allVolumes.map((v) => {
        return createMeshFromVolume(v);
    });

    let zoneMesh = createMeshFromVolume(zone);
    let zoneOBB = createOBBFromMesh(zoneMesh, viewer);

    let resultMesh;

    volumeMeshes.forEach((volume) => {
        volume.updateMatrixWorld(true);

        let volumeOBB = createOBBFromMesh(volume, viewer);

        if (zoneOBB.intersectsOBB(volumeOBB)) {
            console.log(`Collision detected with volume: ${volume.name}`);

            let originalCSGZone;

            if (!resultMesh) {
                originalCSGZone = CSG.fromMesh(zoneMesh);
            } else {
                originalCSGZone = CSG.fromMesh(resultMesh);
            }

            let csgZone = originalCSGZone.clone();

            let csgVolume = CSG.fromMesh(volume);
            csgZone = csgZone.subtract(csgVolume);

            const collisionInfo = calculateCollisionInfo(zoneOBB, volumeOBB);

            const details = getCollisionDetails(zoneOBB, volumeOBB, collisionInfo);
            if (details) {
                console.log(`Point of collide: `, details.collisionPoint);
                console.log(`Side of impact: `, details.side);
                console.log('Volume: ', zone, zoneMesh, zoneOBB);
            }

            const resultGeometry = CSG.toGeometry(csgZone, zoneMesh.matrix);
            const resultMaterial = new MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.001 });
            resultMesh = new Mesh(resultGeometry, resultMaterial);
            resultMesh.applyMatrix4(zoneMesh.matrixWorld);

            const vertices = [];
            const positionAttribute = resultGeometry.getAttribute('position');

            for (let i = 0; i < positionAttribute.count; i++) {
                const vertex = new Vector3();
                vertex.fromBufferAttribute(positionAttribute, i);
                vertices.push([vertex.x, vertex.y, vertex.z]);
            }

            const matrixElements = resultMesh.matrix.elements;
        } else {
            console.log(`Collision not detected`);
        }
    });

    if (resultMesh) {
        let volume = CSG.fromMesh(resultMesh);
        const frame = filterOuterEdges(volume);

        viewer.scene.scene.add(frame);
        viewer.scene.scene.add(resultMesh);

        setTimeout(() => {
            viewer.scene.scene.remove(resultMesh);
            // viewer.scene.scene.remove(frame);
        }, 160);
    }
};

export default calculateZoneCollision;

function filterOuterEdges(csg) {
    const edges = new Map();

    // Goes trough all poygons and all edges
    csg.polygons.forEach((polygon) => {
        const vertices = polygon.vertices;
        for (let i = 0; i < vertices.length; i++) {
            const start = vertices[i].pos;
            const end = vertices[(i + 1) % vertices.length].pos;
            const edgeKey = vertexKey(start, end);

            if (edges.has(edgeKey)) {
                // If bouth polygons have same or look a like normal that might be inner edge
                const edge = edges.get(edgeKey);
                if (edge.normal.dot(polygon.plane.normal) > 0.95) {
                    edges.delete(edgeKey); // Delete becouse that will be inner edge
                }
            } else {
                // Save edge with his normal for later check
                edges.set(edgeKey, { start: start, end: end, normal: polygon.plane.normal });
            }
        }
    });

    // Now we make geometry with outside edges
    const outerEdgesGeometry = new BufferGeometry();
    const positions = new Float32Array(edges.size * 2 * 3);
    let positionIndex = 0;

    edges.forEach((edge) => {
        positions.set([edge.start.x, edge.start.y, edge.start.z], positionIndex);
        positionIndex += 3;
        positions.set([edge.end.x, edge.end.y, edge.end.z], positionIndex);
        positionIndex += 3;
    });

    outerEdgesGeometry.setAttribute('position', new BufferAttribute(positions, 3));

    const lineMaterial = new LineBasicMaterial({ color: 0xff0000 });
    const lineSegments = new LineSegments(outerEdgesGeometry, lineMaterial);
    return lineSegments;
}

function vertexKey(vertex1, vertex2) {
    const minVertex =
        vertex1.x < vertex2.x
            ? vertex1
            : vertex1.x === vertex2.x && vertex1.y < vertex2.y
            ? vertex1
            : vertex1.x === vertex2.x && vertex1.y === vertex2.y && vertex1.z < vertex2.z
            ? vertex1
            : vertex2;
    const maxVertex = vertex1 === minVertex ? vertex2 : vertex1;
    return `${minVertex.x}_${minVertex.y}_${minVertex.z}-${maxVertex.x}_${maxVertex.y}_${maxVertex.z}`;
} 

到目前为止,问题的最佳解决方案,但仍然不够完美。现在我使用 CSG 中的顶点并过滤它们。一切都进展得足够快,但仍然有一些内部问题我仍然需要过滤掉。 Side viewTop view(红框就是我要过滤的BufferGeometry)

© www.soinside.com 2019 - 2024. All rights reserved.