【问题标题】:How to find only the "top" of a mesh in Unity如何在 Unity 中仅找到网格的“顶部”
【发布时间】:2018-02-17 06:30:43
【问题描述】:

我需要在 Unity 中确定构成其“顶部”的多面体(网格)的顶点;即沿 y 轴最远的点。

这样做的原因是我正在创建一个程序环境,其中包含许多活跃的网格动力学;其中一个将海洋塑造成波形。它的效果很好,但也会扭曲水底,这并不理想。

我觉得答案可能涉及某种投影,但我不确定如何。

【问题讨论】:

  • 获取所有顶点,对于每个顶点,将位置转换为世界位置(如果没有旋转则跳过),保留y值最高的那个。

标签: c# unity3d 3d unity5 mesh


【解决方案1】:

免责声明:我以前没有以这种方式使用过网格,但我相信这里有 2 种思维方式可能会让您更进一步地实现完全有效的解决方案。

可能的解决方案:

使用 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

希望对你有帮助。

【讨论】:

  • 这是好材料;但我正在处理任意网格,所以它并不完整。此外,如果我要正确执行此操作,我需要三角形。但是,我想出的答案与此惊人地相似。我把它摆在下面。
  • 如果您只需要检查它们是否在底部上方,您可以创建一个“缓冲区”区域并说所有高于给定值的顶点都被视为顶部的一部分。跨度>
  • @Doh9 我考虑过这一点,但错误的余地太大了。毕竟这是程序生成的——如果可以发生,它就会发生。
  • 取决于可以发生多大的变化以及波浪的中间部分有多大。但是,您似乎已经找到了一个无论这些值如何都有效的解决方案,这当然是更优化的 :-)
【解决方案2】:

这就是我最终的结果。我退后一步,考虑了我想要做的事情的性质,那就是只改变顶面;以及有效的约束,即我需要在 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。

【讨论】:

    【解决方案3】:

    阅读前请注意:这是我最初的方法,在实践中效果很好,但后来发布了一个更简单、更易读的解决方案。

    首先,让我强调一下,我仍然是单元测试这段代码,它可能有错误。到目前为止,一切都很好。

    这就是我所做的。

    我把它写在白板上,发现我正正交地向下投影 y。因此,我需要做的第一件事是获取每个三角形的平均 y 值。

    (请注意,这意味着在自相交网格的情况下这将毫无用处;要处理这些问题,您必须对“顶部”的实际含义提出更好的定义。)

    之后,我根据平均 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 函数来查找顶面。在其他情况下,这个算法有十几种不同的方式可能会被扭曲,所以我试图很好地评论它。希望它会在未来帮助其他人。

    当我绝对可靠地确信这在所有合理的情况下都有效时,我会接受这个作为答案;但是,任何有建议的人提供进一步的意见,总是受欢迎的。

    【讨论】:

      猜你喜欢
      • 2020-04-24
      • 2018-10-30
      • 1970-01-01
      • 1970-01-01
      • 2020-03-13
      • 2019-11-17
      • 2019-03-25
      • 1970-01-01
      • 2018-02-09
      相关资源
      最近更新 更多