You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js中提取复杂BufferGeometry外部边缘的技术问询

问题描述

我正在开发一个基于Three.js的区域创建与碰撞检测项目,目前已实现碰撞处理并生成BufferGeometry。我的目标是可视化区域的边界框,以清晰划分区域范围。对于简单几何体,我可以轻松创建包围盒(效果如图所示),但面对复杂几何体时,包围盒无法准确呈现区域的实际复杂形状。

为此,我尝试通过顶点创建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
}

内容的提问来源于stack exchange,提问作者Slaven Soldo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:21:22