three ConvexHull 凸包

303 阅读6分钟

在 Three.js 中,ConvexHull 是一个实用工具,用于计算一组点的凸包。凸包是围绕一组点的最小凸多边形或多面体。在三维空间中,ConvexHull 通常用于生成由点云包围的凸形几何体。
在 Three.js 中,计算几何体的 凸包(Convex Hull)通常是指找到一个点集的最小凸多面体,它包围了所有的点。Three.js 提供了一个 ConvexHull 相关的 API,能够帮助开发者从一组点中计算出凸包。

ConvexHull 有六个属性二十一个方法

    ConvexHull()
    创建一个 ConvexHull 实例。
    // 生成随机点云 示例 1
    const points = [];
    for (let i = 0; i < 30; i++) {
        points.push(new THREE.Vector3(
            Math.random() * 4 - 2, // X 坐标 [-2, 2]
            Math.random() * 4 - 2, // Y 坐标 [-2, 2]
            Math.random() * 4 - 2  // Z 坐标 [-2, 2]
        ));
    }
    // 验证点云有效性
    const validPoints = points.filter(point => {
        if (isNaN(point.x) || isNaN(point.y) || isNaN(point.z)) {
            console.error('Invalid point detected:', point);
            return false;
        }
        return true;
    });
    // 去除重复点
    const uniquePoints = [];
    validPoints.forEach(point => {
        if (!uniquePoints.some(p => p.equals(point))) {
            uniquePoints.push(point);
        }
    });
    // 显示原始点云
    const pointGeometry = new THREE.BufferGeometry().setFromPoints(uniquePoints);
    const pointMaterial = new THREE.PointsMaterial({ color: 0x00ff00, size: 0.1 });
    const pointCloud = new THREE.Points(pointGeometry, pointMaterial);
    scene.add(pointCloud);
    // 计算凸包
    const hull = new ConvexHull().setFromPoints(uniquePoints);
    // 提取顶点和面信息,生成几何体
    const vertices = [];
    hull.faces.forEach(face => {
        if (face.edge) {
            let edge = face.edge;
            do {
                const vertex = edge.head().point;// 获取点的x y z 座标
                if (!isNaN(vertex.x) && !isNaN(vertex.y) && !isNaN(vertex.z)) {
                    vertices.push(vertex.x, vertex.y, vertex.z);
                } else {
                    console.error('Invalid vertex in hull:', vertex);
                }
                edge = edge.next;
            } while (edge !== face.edge);
        }
    });
    // 验证顶点数据
    if (vertices.length === 0) {
        console.error('No valid vertices extracted for the convex hull.');
    } else if (vertices.some(coord => isNaN(coord))) {
        console.error('Invalid coordinate found in vertices:', vertices);
    }
    // 创建凸包几何体
    const hullGeometry = new THREE.BufferGeometry();
    if (vertices.length > 0) {
        hullGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
        try {
            hullGeometry.computeBoundingSphere(); // 确保没有 NaN 错误
        } catch (e) {
            console.error('Error computing bounding sphere:', e);
        }
    } else {
        console.error('No vertices available for geometry.');
    }
    // 创建网格并添加到场景
    const hullMaterial = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        transparent: true,
        opacity: 0.5,
        side: THREE.DoubleSide,
    });
    const hullMesh = new THREE.Mesh(hullGeometry, hullMaterial);
    scene.add(hullMesh);
    
    
    
    // 生成随机点云 示例 2
    const points = [];
    for (let i = 0; i < 30; i++) {
        points.push(new THREE.Vector3(
            Math.random() * 4 - 2, // X 坐标 [-2, 2]
            Math.random() * 4 - 2, // Y 坐标 [-2, 2]
            Math.random() * 4 - 2  // Z 坐标 [-2, 2]
        ));
    }
    // 验证点云有效性
    const validPoints = points.filter(point => {
        if (isNaN(point.x) || isNaN(point.y) || isNaN(point.z)) {
            console.error('Invalid point detected:', point);
            return false;
        }
        return true;
    });
    // 去除重复点
    const uniquePoints = [];
    validPoints.forEach(point => {
        if (!uniquePoints.some(p => p.equals(point))) {
            uniquePoints.push(point);
        }
    });
    // 使用 THREE.ConvexGeometry 创建凸包几何体
    const convexGeometry = new ConvexGeometry(uniquePoints);
    // 创建凸包网格并添加到场景
    const hullMaterial = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        transparent: true,
        opacity: 0.5,
        side: THREE.DoubleSide,
    });
    const hullMesh = new THREE.Mesh(convexGeometry, hullMaterial);
    scene.add(hullMesh);

属性

  • assigned : VertexList 该 vertex list 包含分配给面的所有顶点。默认是一个空的顶点列表。
  • faces : Array 生成的凸包面。默认是一个空数组。
  • newFaces : Array 该数组保存在单次迭代中生成的面。默认是一个空数组。
  • tolerance : Float 用于内部比较运算的 epsilon 值。该值的计算取决于几何形状的大小。默认值为 -1。
  • unassigned : VertexList 该 vertex list 包含未分配给面的所有顶点。默认是一个空的顶点列表。
  • vertices : Array 给定几何数据的内部表示( vertices 数组)。

方法

  • addAdjoiningFace ( eyeVertex : VertexNode, horizonEdge : HalfEdge ) : HalfEdge eyeVertex - 添加到凸包的顶点。 horizonEdge - 地平线的单个边缘。创建一个面,顶点顺序为 'eyeVertex.point'、'horizonEdge.tail' 和 'horizonEdge.head',顺序为逆时针(CCW)。所有半边按照逆时针顺序创建,因此该面始终指向凸包的外部。
  • addNewFaces ( eyeVertex : VertexNode, horizonEdge : HalfEdge ) : this eyeVertex - 添加到凸包的顶点。 horizon - 形成地平线的半边数组。 将 'horizon.length' 个面添加到凸包,每个面将与地平线上的相对面以及左/右相邻的面连接。
  • addVertexToFace ( vertex : VertexNode, face : Face ) : this vertex - 要添加的顶点。 face - 目标面。 将一个顶点添加到 “分配(assigned)” 顶点列表,并将其分配给给定的面。
  • addVertexToHull ( eyeVertex : VertexNode ) : this eyeVertex - 添加到凸包的顶点。 使用以下算法向凸包添加顶点:
    • 计算“地平线”,这是一系列半边。要使一条边属于这个组,它必须是连接一个能够看到 'eyeVertex' 的面和一个不能看到 'eyeVertex' 的面的边。
    • 所有能够看到 'eyeVertex' 的面都会从已分配顶点列表中移除其可见的顶点。
    • 使用“地平线”和 'eyeVertex' 的每条边创建一组新的面。每个面都与相对的地平线面以及左/右相邻的面连接。
    • 从所有可见面中移除的顶点将被分配给新的面,如果可能的话。
  • cleanup () : this 计算凸包后清理内部属性。
  • compute () : this 开始执行快速凸包算法。
  • computeExtremes () : Object 计算将用于计算初始凸包的极值(最小/最大向量)。
  • computeHorizon ( eyePoint : Vector3, crossEdge : HalfEdge, face : Face, horizon : Array ) : this 计算一个逆时针(CCW)顺序的半边链,称为“地平线”('horizon')。要使一条边成为地平线的一部分,它必须连接一个能够看到 'eyePoint' 的面和一个不能看到 'eyePoint' 的面。
    • eyePoint - 点的 3D 坐标。
    • crossEdge - 用于跳转到当前面的边。
    • face - 当前正在测试的面。
    • horizon - 按 CCW 顺序构成地平线一部分的边。
  • computeInitialHull () : this 计算初始的单纯形,将所有可能成为凸包一部分的点分配给其面。
  • containsPoint ( point : Vector3 ) : this point - 3D 空间中的一个点。 返回 true 如果定点在此凸包内。
  • deleteFaceVertices ( face : Face, absorbingFace : Face ) : this face - 给定的面。 absorbingFace - 尝试吸收第一个面的顶点的可选面。 删除 “面(face)” 能够看到的所有可见顶点。
    • 如果 'absorbingFace' 不存在,则所有已移除的顶点将被添加到 'unassigned' 顶点列表。
    • 如果 'absorbingFace' 存在,则该方法将分配所有 'face' 中可以看到 'absorbingFace' 的顶点。
    • 如果一个顶点不能看到 'absorbingFace',则将其添加到 'unassigned' 顶点列表。
  • intersectRay ( ray : Ray, target : Vector3 ) : Vector3 ray - 给定的射线。 target - 表示交点的目标向量。 与该凸包执行光线相交测试。如果没有找到交集则返回 null。
  • makeEmpty () : this 使这个凸包为空。
  • nextVertexToAdd () : VertexNode 查找下一个顶点以使用当前凸包创建面。
    • 让初始面成为存在于“已分配(assigned)”顶点列表中的第一个面。
    • 如果面不存在则返回,因为没有顶点剩下。
    • 否则,对于面能看到的每个顶点,找到距离它最远的那个。
  • reindexFaces () : this 从内部面列表中移除不活跃的(例如已删除的)面。
  • removeAllVerticesFromFace ( face : Face ) : VertexNode face - 给定的面。 移除给定面能够看到的所有可见顶点,这些顶点存储在“已分配(assigned)”顶点列表中。
  • removeVertexFromFace ( vertex : VertexNode, face : Face ) : this vertex - 要删除的顶点。 face - 目标面。 从“已分配”顶点列表和给定面中移除一个顶点。在移除后,它还确保从“面”到它在“已分配”中看到的第一个顶点的链接被正确连接。
  • resolveUnassignedPoints ( newFaces : Array ) : this newFaces - 一组新的面。 尽可能从未分配的顶点列表中重新分配顶点给新的面。
  • setFromObject ( object : Object3D ) : this object - 用于计算凸包的 Object3D 计算 Object3D 的凸包(包括其子元素),考虑到对象及其子元素的世界变换。
  • setFromPoints ( points : Array ) : this points - 生成的凸包将包含的 Vector3s 数组 计算给定点数组的凸包。