在unity向量空间内绘制几何(3):通过三角形重心坐标计算任意形状网格上的随机坐标点

在unity向量空间内绘制几何(3):通过三角形重心坐标计算任意形状网格上的随机坐标点_第1张图片

1,理解三角形的重心坐标(面积坐标)

假如有任意一个三角形ABC,设它的三个顶点为a,b,c,三角形内任意一点为p,三角形PBC,PCA,PAB面积之比为 λ1:λ2:λ3 λ1+λ2+λ3=1 。则点p与三个顶点a,b,c有以下关系:

p=λ1a+λ2b+λ3c

λ1λ2λ3 )为p的重心坐标。

以下代码非原创,来源已忘(sorry-_-;),注释为本人所加

2,计算步骤

大体分为六个步骤:

    public Vector3 GenerateRandomPoint(Mesh mesh)
    {
        // 1 - Calculate Surface Areas
        //计算网格的平面面积
        float[] triangleSurfaceAreas = CalculateSurfaceAreas(mesh);

        // 2 - Normalize area weights
        //将平面面积化为百分比权重
        float[] normalizedAreaWeights = NormalizeAreaWeights(triangleSurfaceAreas);

        // 3 - Generate 'triangle selection' random #
        //计算一个0至1之间的任意浮点数
        float triangleSelectionValue = Random.value;

        // 4 - Walk through the list of weights to select the proper triangle
        //将此浮点数与权重比较,选出一个随机三角形
        int triangleIndex = SelectRandomTriangle(normalizedAreaWeights, triangleSelectionValue);

        // 5 - Generate a random barycentric coordinate
        //计算一个任意的重心坐标Vector3,既是三个0-1之间的随机浮点数
        Vector3 randomBarycentricCoordinates = GenerateRandomBarycentricCoordinates();

        // 6 - Using the selected barycentric coordinate and the selected mesh triangle, convert
        //     this point to world space.
        //将重心坐标赋给随机选出的三角形并返回世界坐标
        return ConvertToLocalSpace(randomBarycentricCoordinates, triangleIndex, mesh);
    }

3,分步详解:

1,计算网格平面面积

    private float[] CalculateSurfaceAreas(Mesh mesh)
    {
    // mesh.triangles是一个数组,大小为网格内所有三角形的三个顶点的总数,除以3后即为网格上的三角形数量。
        int triangleCount = mesh.triangles.Length / 3;

        float[] surfaceAreas = new float[triangleCount];

//依次计算每个三角形的每个顶点的坐标
        for (int triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++)
        {
            Vector3[] points = new Vector3[3];
            points[0] = mesh.vertices[mesh.triangles[triangleIndex * 3 + 0]];
            points[1] = mesh.vertices[mesh.triangles[triangleIndex * 3 + 1]];
            points[2] = mesh.vertices[mesh.triangles[triangleIndex * 3 + 2]];

            // calculate the three sidelengths and use those to determine the area of the triangle
            // http://www.wikihow.com/Sample/Area-of-a-Triangle-Side-Length
//通过向量相减法则,同一个起点出发的两个向量相减得出一条将与它们组成三角形的第三个向量,并由magnitude得出它的长度。
            float a = (points[0] - points[1]).magnitude;
            float b = (points[0] - points[2]).magnitude;
            float c = (points[1] - points[2]).magnitude;

//通过海伦公式得出此三角形面积
            float s = (a + b + c) / 2;

            surfaceAreas[triangleIndex] = Mathf.Sqrt(s*(s - a)*(s - b)*(s - c));
        }

        return surfaceAreas;
    }

2,将平面面积化为百分比权重,用以随机选取一个三角形

    private float[] NormalizeAreaWeights(float[] surfaceAreas)
    {
        float[] normalizedAreaWeights = new float[surfaceAreas.Length];

        float totalSurfaceArea = 0;
        foreach (float surfaceArea in surfaceAreas)
        {
            totalSurfaceArea += surfaceArea;
        }

        for (int i = 0; i < normalizedAreaWeights.Length; i++)
        {
            normalizedAreaWeights[i] = surfaceAreas[i] / totalSurfaceArea;
        }

        return normalizedAreaWeights;
    }

3,计算一个0至1之间的任意浮点数,用以随机选取一个三角形

        float triangleSelectionValue = Random.value;

4,将此浮点数与权重比较,选出一个随机三角形

    private int SelectRandomTriangle(float[] normalizedAreaWeights, float triangleSelectionValue)
    {
        float accumulated = 0;

        for (int i = 0; i < normalizedAreaWeights.Length; i++)
        {
            accumulated += normalizedAreaWeights[i];
//每个三角形面积权重依次相加,如果大于上面随机出的浮点数,则返回index
            if (accumulated >= triangleSelectionValue)
            {
                return i;
            }
        }

        // unless we were handed malformed normalizedAreaWeights, we should have returned from this already.
        throw new System.ArgumentException("Normalized Area Weights were not normalized properly, or triangle selection value was not [0, 1]");
    }

5,计算一个任意的重心坐标Vector3,既是三个0-1之间的随机浮点数

    private Vector3 GenerateRandomBarycentricCoordinates()
    {
        Vector3 barycentric = new Vector3(Random.value, Random.value, Random.value);

        while (barycentric == Vector3.zero)
        {
            // seems unlikely, but just in case...
            barycentric = new Vector3(Random.value, Random.value, Random.value);
        }

        // normalize the barycentric coordinates. These are normalized such that x + y + z = 1, as opposed to
        // normal vectors which are normalized such that Sqrt(x^2 + y^2 + z^2) = 1. See:
        // http://en.wikipedia.org/wiki/Barycentric_coordinate_system
//由于重心坐标的法则,三个数值相加必须为1,所以在这里将它们化为相加为1的分数
        float sum = barycentric.x + barycentric.y + barycentric.z;

        return barycentric / sum;
    }

6,将重心坐标赋给随机选出的三角形并返回世界坐标

    private Vector3 ConvertToLocalSpace(Vector3 barycentric, int triangleIndex, Mesh mesh)
    {
        Vector3[] points = new Vector3[3];
        points[0] = mesh.vertices[mesh.triangles[triangleIndex * 3 + 0]];
        points[1] = mesh.vertices[mesh.triangles[triangleIndex * 3 + 1]];
        points[2] = mesh.vertices[mesh.triangles[triangleIndex * 3 + 2]];

        return (points[0] * barycentric.x + points[1] * barycentric.y + points[2] * barycentric.z);
    }

2017-2-15 更新

由于mesh.vertices[] 返回的是其相对于网格的相对坐标,如果想获取网格上坐标点正确的世界坐标,需加上网格的世界坐标:

GenerateRandomPoint(someMesh)+someMesh.transform.position;

另外,mesh.vertices[]的值不受网格transform的scale与rotation变化的影响。

你可能感兴趣的:(Unity3D&几何)