1,三角形的重心坐标(面积坐标)
假如有任意一个三角形ABC,设它的三个顶点为a,b,c,三角形内任意一点为p,三角形PBC,PCA,PAB面积之比为
λ
1
:
λ
2
:
λ
3
\lambda_1:\lambda_2:\lambda_3
λ1:λ2:λ3且
λ
1
+
λ
2
+
λ
3
=
1
\lambda_1+\lambda_2+\lambda_3=1
λ1+λ2+λ3=1。则点p与三个顶点a,b,c有以下关系:
p
=
λ
1
a
+
λ
2
b
+
λ
3
c
p=\lambda_1a+\lambda_2b+\lambda_3c
p=λ1a+λ2b+λ3c
(
λ
1
,
λ
2
,
λ
3
\lambda_1,\lambda_2,\lambda_3
λ1,λ2,λ3)为p的重心坐标。
重心坐标可以在忽略三角形在3D空间中的角度,只依赖于三角形三个顶点位置来对三角形内任意一点进行定位,另外在Unity中每个三角形的顶点位置比较好获取,所以重心坐标在某些情况下特别好用。
2,演示函数:随机选择网格上的一个三角形并计算一个随机重心坐标点最后返回它的相对位置坐标
以下代码非原创,来源已忘(sorry-_-),注释为本人所加
大体分为六个步骤:
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);
}
分步详解:
1,计算网格平面面积
private float[] CalculateSurfaceAreas(Mesh mesh)
{
// mesh.triangles是一个数组,数组长度为网格内所有三角形*3(三个顶点),除以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 p = (a + b + c) / 2;
surfaceAreas[triangleIndex] = Mathf.Sqrt(p*(p - a)*(p - b)*(p - 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]];
//返回的是相对于游戏物体的位置坐标,可通过Unity的方法再转为世界坐标
return (points[0] * barycentric.x + points[1] * barycentric.y + points[2] * barycentric.z);
}
3,演示函数:输入重心坐标与三个顶点位置,返回相对坐标
private Vector3 Barycentric2Local(Vector3 bary,Vector3 vertices)
{
//重心坐标归一化
float sum = bary.x + bary.y + bary.z;
bary = bary / sum;
//计算相对坐标
vertices = new Vector3(vertices.x*bary.x,vertices.y*bary.y,vertices.z*bary.z);
return vertices;
}
维护日志:
2017-2-15:review
2020-3-14:增删查改