Physx之解决三角形顶点相近卡住的问题

[TOC]
今天遇到一个问题,角色卡在一个模型边上,在PVD看模型也比较正常,一直调试跟踪,最终找到了问题所在。

原因

1.问题出在最后一个sweepCapsuleTriangles函数里面,代码如下:

#ifdef __SPU__
                Vec3V dummy1, dummy2;
                FloatV result = distanceSegmentTriangleSquared(
                    V3LoadU(capsule.p0), V3LoadU(capsule.p1), V3LoadU(p0), V3LoadU(p1), V3LoadU(p2), dummy1, dummy2);
                PxReal dist2 = FStore(result);
    #else
                // AP: switching to SIMD version produced -25% regression in Sweep*SphereHfld
                PxReal dist2 = distanceSegmentTriangleSquared(capsule.p0, segmentExtent, p0, p1 - p0, p2 - p0);
    #endif
                if (dist2<=r2)
                {
                    hitIndex    = i;
                    t            = 0.0f;
                    normal        = -unitDir;
                    outFlags    = PxHitFlag::eDISTANCE|PxHitFlag::eNORMAL;
                    _triNormal    = denormalizedNormal.getNormalized();
                    return true;
                }

当p0、p1和p2有有两个点相同的时候,会导致p1-p0和p2-p0其中一个向量为0,得到的dist2为负无穷大,所以就会执行代码中的条件语句。最终结果是move函数移动成功了,但是只移动了0的位移,表现上就是一直卡在那个地方。

  1. 至于顶点一样的情况,其实并不是完全一样,而是有很小的差异。再加上浮点数运算会丢失精度,这个很小的差异被抹掉了,所以到上面代码的时候就出现相同的点了。

计算的代码位置(没看太明白):

PX_FORCE_INLINE void Gu::TriangleMesh::computeWorldTriangle(PxTriangle& worldTri, PxTriangleID triangleIndex, const Cm::Matrix34& worldMatrix, PxU32* PX_RESTRICT vertexIndices, PxU32* PX_RESTRICT adjacencyIndices) const
    {
        PxU32 vref0, vref1, vref2;
        if(mMesh.has16BitIndices())
        {
            const Gu::TriangleT& T = ((const Gu::TriangleT*)getTrianglesFast())[triangleIndex];
            vref0 = T.v[0];
            vref1 = T.v[1];
            vref2 = T.v[2];
        }
        else
        {
            const Gu::TriangleT& T = ((const Gu::TriangleT*)getTrianglesFast())[triangleIndex];
            vref0 = T.v[0];
            vref1 = T.v[1];
            vref2 = T.v[2];
        }
        const PxVec3* PX_RESTRICT vertices = getVerticesFast();
        worldTri.verts[0] = worldMatrix.transform(vertices[vref0]);
        worldTri.verts[1] = worldMatrix.transform(vertices[vref1]);
        worldTri.verts[2] = worldMatrix.transform(vertices[vref2]);
    
        if(vertexIndices)
        {
            vertexIndices[0] = vref0;
            vertexIndices[1] = vref1;
            vertexIndices[2] = vref2;
        }

        if(adjacencyIndices)
        {
            if(mMesh.getAdjacencies())
            {
                adjacencyIndices[0] = mMesh.getAdjacencies()[triangleIndex*3 + 0];
                adjacencyIndices[1] = mMesh.getAdjacencies()[triangleIndex*3 + 1];
                adjacencyIndices[2] = mMesh.getAdjacencies()[triangleIndex*3 + 2];
            }
            else
            {
                adjacencyIndices[0] = 0xffffffff;
                adjacencyIndices[1] = 0xffffffff;
                adjacencyIndices[2] = 0xffffffff;
            }
        }
    }
  • 问题数据

出现问题的时候,断点的数据如下:

vertices[vref0] = {x = -4.10000086, y = -0.200000167, z = -3.56512594}
    vertices[vref1] = {x = -4.10000086, y = -0.200000077, z = -3.56512594}
    vertices[vref2] = {x = -4.10000086, y = -0.200000212, z = -4.10000229}

    worldMatrix.transform.base3 = {x=72.3379974 y=4.76143503 z=18.5843773 }

    // 得到这个三角形前两个点完全一样
    worldTri.verts[0] = { x = 68.237990, y = 4.56143475, z = 15.0192509}
    worldTri.verts[1] = { x = 68.237990, y = 4.56143475, z = 15.0192509}
    worldTri.verts[2] = { x = 68.237990, y = 4.56143475, z = 14.4843750}

简单来说就是:

4.76143503 + -0.200000167 = 4.56143475
    4.76143503 + -0.200000077 = 4.56143475

至于为什么请参考另外一篇文章[1] 为什单精度浮点数的有效位时7位

解决

  • 1.把顶点传到PhysX之前,先检查一遍。可以删掉重新组织Vertex和Index,我简单处理的方法是把两个顶点很近的时候,标记为同一个索引,这样的三角形比较容易标识

    if (fMinDist <= 1e-5)
                  {
                      // set the same index, and then cooking will process it. eREMOVE_DUPLICATED_TRIANGLES
                      pIndices[3 * i + nIndexIndex1] = pIndices[3 * i + nIndexIndex2];
                  }
  • 2.PhysxCooking的参数增加去重的Flag,这样上面的三角形就会被PhysX剔除,从而就解决了这样的问题:如下
physx::PxCookingParams cookingParams(scale);
        cookingParams.buildTriangleAdjacencies = true;
        cookingParams.meshPreprocessParams = PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eWELD_VERTICES |
            PxMeshPreprocessingFlag::eREMOVE_UNREFERENCED_VERTICES | PxMeshPreprocessingFlag::eREMOVE_DUPLICATED_TRIANGLES);

参考

[1] 为什么单精度浮点数的精度是7位