我需要确定Unity中多边形(网格)的顶点,它构成了它的顶部&#34 ;;也就是说,沿着y轴的最远点。
这样做的原因是我正在创建一个程序环境,其中有许多活动的网格动态;其中一个将海洋塑造成波形。它工作得很漂亮,但也会扭曲水底,这并不理想。
我觉得答案可能涉及某种投射,但我不确定如何。
答案 0 :(得分:1)
免责声明:我之前没有以这种方式使用网格,但我认为这有两种思维方式可以让您进一步朝着完全可行的解决方案发展。
可能的解决方案:
使用Mesh.Bounds属性。
虽然我怀疑这只适用于平面。
来源:https://docs.unity3d.com/ScriptReference/Mesh-bounds.html
使用Mesh.Vertices,并根据您使用的轴作为顶部(y轴),使用每个数组索引中的2个其他轴(x和z)来计算网格特定部分中的最高点。制作这些和tadah的完整集合,你有表面。
Mesh.Vertices返回一个Vector3数组。
想象一下你有这些坐标。
Vector3 (443, 31, 543);
Vector3 (443, 32, 543);
Vector3 (443, 33, 543);
Vector3 (443, 34, 543);
Vector3 (443, 35, 543);
这里的最高坐标是y为35的坐标,假设正y轴是您认为的顶部。
可能需要将某些顶点组合在一起,具体取决于您给出的顶点数,以便Vector3 (443, 35, 543);
和Vector3 (443.1, 35, 543.2);
处于相同的比较中。
来源:https://docs.unity3d.com/ScriptReference/Mesh-vertices.html
希望这可以帮到你。
答案 1 :(得分:0)
请在阅读之前注意:这是我原来的方法,在实践中效果很好,但结果却是一个更简单,更易读的解决方案。
首先,请允许我强调一下,我仍然是单元测试这个代码,它可能有错误。到目前为止,它一直很好。
这就是我所做的。
我在白板上写下来,注意到我正向下投射y。因此,我需要做的第一件事是获得每个三角形的平均y值。
(注意这意味着在自相交网格的情况下这将是无用的;为了解决这些问题,你必须为什么"顶部和&#34提供一个更好的定义? #34;实际上意味着。)
之后,我根据平均y值对三角形进行了排序,列表前面的y值最高。
之后,这是一个排序问题!对于每个三角形,如果它没有交叉我之前保留的三角形,那么我将它添加到顶侧三角形列表中。我为此使用了point-in-triangle test。如果它确实相交,则它位于我的一个顶侧三角形之下,可以跳过。
另一种不起作用的情况是三角形重叠但不相交的其他三角形的实例;这意味着边缘相互交叉的任何情况,但没有顶点是"内部"另一个三角形的边界。有点像大卫之星的形状;此代码不会将它们检测为交叉。
正如我所说,我仍然在可预见的情况下进行单元测试;但我的代码如下。我选择了一个功能范例,因为它可以让事情变得容易,并适用于现代游戏软件。请注意,您需要为任何类型的网格动态记住它;每次想要改变网格时,你都不想扫描每个三角形!
/// <summary>
/// Gets the average y-height of a triangle of Vector3s.
/// </summary>
static Func<Vector3, Vector3, Vector3, float> yHeight = (a, b, c) => (a.y + b.y + c.y)/3f;
/// <summary>
/// Projects a provided triangle onto the X-Z plane. (If you need to project onto any other plane, this is the function to alter or
/// replace.)
/// </summary>
static Func<Vector3[], Vector3[]> ProjectToY = (points) =>
new Vector3[]{
new Vector3(points[0].x, 0, points[0].z),
new Vector3(points[1].x, 0, points[1].z),
new Vector3(points[2].x, 0, points[2].z)
};
/// <summary>
/// Determines if point p is on the same side of any given edge, as the opposite edge.
/// </summary>
static Func<Vector3, Vector3, Vector3, Vector3, bool> SameSide = (p1, p2, a, b) => {
return Vector3.Dot(
Vector3.Cross (b - a, p1 - a),
Vector3.Cross (b - a, p2 - a)
) >= 0;
};
/// <summary>
/// Determines whether a point is inside the triangle formed by given Vector3s
/// </summary>
static Func<Vector3, Vector3, Vector3, Vector3, bool> PointInTriangle = (p, a, b, c) => {
return SameSide(p, a, b, c) && SameSide(p, b, a, c) && SameSide(p, c, a, b);
};
/// <summary>
/// Determines whether triangle of Vector3s one intersects triangle of Vector3s two. (Note: Does not check the other way around.
/// Presumes both are on the same plane.)
/// </summary>
static Func<Vector3[], Vector3[], bool> TriangleInTriangle = (one, two) =>
PointInTriangle(one[0], two[0], two[1], two[2]) ||
PointInTriangle(one[1], two[0], two[1], two[2]) ||
PointInTriangle(one[2], two[0], two[1], two[2])
;
/// <summary>
/// Determines whether either of two triangles intersect the other. The one exception would be a case where the triangles overlap,
/// but do not intersect one another's points. This would require segment intersection tests.
/// </summary>
static Func<Vector3[], Vector3[], bool> TrianglesIntersect = (one, two) =>
TriangleInTriangle(one, two) || TriangleInTriangle(two, one)
;
/// <summary>
/// Organizes a dictionary of triangle indices to average distances by distance, with the furthest at the front.
/// </summary>
static Func<Vector3[], int[], Dictionary<int, float>> OrderByHeight = (vertices, triangles) => {
Dictionary<int, float> height = new Dictionary<int, float>();
for(int i = 0; i < triangles.Length; i += 3)
height.Add(i / 3, yHeight(vertices[triangles[i]], vertices[triangles[i+1]], vertices[triangles[i + 2]]));
return height;
};
/// <summary>
/// Sorts a dictionary of triangle indices to average heights, to a list of the same key value pairs, with the pairs of highest height at the front.
/// </summary>
static Func<Dictionary<int, float>, List<KeyValuePair<int, float>>> SortByHeight = (height) => {
List<KeyValuePair<int, float>> orderedHeight = height.ToList();
orderedHeight.Sort(
delegate(KeyValuePair<int, float> a, KeyValuePair<int, float> b) {
return a.Value > b.Value ? -1 : 1;
}
);
return orderedHeight;
};
/// <summary>
/// Determines whether a provided triangle, in KeyValuePair form of triangle to average distance, is in front of all triangles in a list of pairs.
/// </summary>
static Func<KeyValuePair<int, float>, List<KeyValuePair<int, float>>, Vector3[], bool> IsInFront = (pair, top, vertices) => {
foreach (KeyValuePair<int, float> prevPair in top) {
if (TrianglesIntersect (
ProjectToY (
new Vector3[]{ vertices [pair.Key * 3 + 0], vertices [pair.Key * 3 + 1], vertices [pair.Key * 3 + 2] }
),
ProjectToY (
new Vector3[] {
vertices [prevPair.Key * 3 + 0],
vertices [prevPair.Key * 3 + 1],
vertices [prevPair.Key * 3 + 2]
}
))) {
return false;
}
}
return true;
};
/// <summary>
/// Given a list of triangle indices to average heights, sorted by height with highest first, finds the triangles which are unoccluded by closer triangles.
/// </summary>
static Func<List<KeyValuePair<int, float>>, Vector3[], List<KeyValuePair<int, float>>> GetUnoccludedTriangles = (orderedHeight, vertices) => {
List<KeyValuePair<int, float>> top = new List<KeyValuePair<int, float>> ();
foreach (KeyValuePair<int, float> pair in orderedHeight) {
if(IsInFront(pair, top, vertices))
top.Add(pair);
}
return top;
};
/// <summary>
/// Converts indices of triangles to indices of the first vertex in the triangle.
/// </summary>
static Func<List<KeyValuePair<int, float>>, int[], int[]> ConvertTriangleIndicesToVertexIndices = (top, triangles) => {
int[] topArray = new int[top.Count * 3];
for(int i = 0; i < top.Count; i++) {
topArray[i * 3 + 0] = triangles[top[i].Key * 3 + 0];
topArray[i * 3 + 1] = triangles[top[i].Key * 3 + 1];
topArray[i * 3 + 2] = triangles[top[i].Key * 3 + 2];
}
return topArray;
};
//This seems to work well; but could work better. See to testing it thoroughly, and making it as functional, bulletproof, and short as possible.
/// <summary>
/// Determines the unoccluded "top" of a mesh of triangles, and returns it.
/// </summary>
static Func<Vector3[], int[], int[]> FindTop = (vertices, triangles) => {
return ConvertTriangleIndicesToVertexIndices(
GetUnoccludedTriangles(
SortByHeight(
OrderByHeight(vertices, triangles)
),
vertices),
triangles);
};
您希望使用网格中的原始数据调用FindTop函数来查找顶部曲面。这种算法在其他情况下有十几种不同的扭曲方式,因此我试图对其进行评论。希望将来可以帮助其他人。
当我绝对可靠地确信这在合理的情况下有效时,我会接受这个作为答案;但是,任何有一分钱建议的人都会欢迎进一步的投入。
答案 2 :(得分:0)
这就是我最终的目标。我退后一步,考虑了我试图做的事情的本质,这只是改变了顶面;以及我需要做的有效约束,以及CPU方面的大量限制。我还认为我之前回答中的大部分材料都是多余的,因为我在程序构建网格时已经完成了相同的操作。
法线始终面向多边形可见的方向;所以他们是我解决方案的关键。我在一个LINQ查询中完成了它,可以在这里找到。
public override void GetAppropriateIndices (Mesh mesh)
{
this.indices = new HashSet<int> (mesh.triangles.AsEnumerable ().Distinct ().Where (index =>
mesh.normals [index] == Vector3.up
).ToList());
}
或者,更灵活,
public override void GetAppropriateIndices (Mesh mesh)
{
this.indices = new HashSet<int> (mesh.triangles.AsEnumerable ().Distinct ().Where (index =>
Vector3.Angle(mesh.normals [index], Vector3.up) < ε
).ToList());
}
其中ε是您愿意承受角度的最大余地。从技术上讲,第二种解决方案更好,因为它考虑了浮点错误,并且事实上并非所有法线都正是我们所说的应该是,在任何语言,API或范例中。
所有测试都已通过。我有一个程序生成的海洋切片,只有动画波浪穿过它的表面(减去用于测试目的的战略切口。)我工作解决方案的图片:
从下面开始,未应用网格动态:
你可以在这里看到,由于网格的法线在表面下方不够接近Vector3.up,因此它不会被修改并保持矩形。
最后,当我将ε拨到90f以上时会发生什么:
我不认为这绝对适用于所有情况,但它们确实有效地完成了工作(所有的功能现在只是一条LINQ线......怎么回事?)并且很多更具可读性。
在我忘记之前,纹理信用转到Textures.com。