【问题标题】:Finding an oriented bounding box for a set of 3D points in Unity在 Unity 中为一组 3D 点查找定向边界框
【发布时间】:2019-10-12 12:02:59
【问题描述】:

我有一组 3D 点,或者实际上是小球体,我需要使用 Unity 3D 用尽可能小的 3D 框将它们包围起来。

在封闭框只能移动和缩放的情况下,解决方案非常简单,您只需遍历所有点并封装每个点。但我还需要为盒子找到最佳的方向。

所以,为了用 ASCII 说明问题,给定一个只有两点的基本 2D 场景:

Y
| * (0,1)
|
|
|
|               * (1,0)
-------------------- X

使用常规增长的边界框,您最终会得到一个包含大部分空白空间的非常大的封闭框,而在这种情况下,我需要一个非常薄且围绕 Z 轴旋转约 45 度的框。基本上只是一条连接两点的线。

我不知道有多少点需要组合在一起。如前所述,它必须在 3D 中工作。

到目前为止,我只尝试了不旋转封装盒以获得最佳配合的基本方法。结果与我所需要的相差甚远。

我正在考虑一种基于遗传算法的蛮力方法,在这种方法中,我生成大量随机框,然后简单地选择面积最小的框,同时仍然包含所有点。但是,这太慢了。

GameObject go = points[0];
Bounds b = new Bounds(go.transform.position,go.transform.localScale);

for (int i=1;i<points.Count;i++)
{
    go = points[i];
    b.Encapsulate(new Bounds(go.transform.position, go.transform.localScale));
}

GameObject containingBox = Instantiate(boxPrefab);
containingBox.transform.position = b.center;
containingBox.transform.localScale = b.size;
containingBox.transform.rotation= Quaternion.Identity; //How to calculate?

【问题讨论】:

  • 这是一个有趣的问题,但它可能过于宽泛以至于不适合堆栈溢出。如果您可以将其分解为更小的问题,我们或许可以为您提供帮助。
  • 这篇维基百科文章可能会对您有所帮助:Minimum bounding box algorithms
  • 是的,你所要求的实际上是非常复杂的。搜索Joseph O'Rourke minimal volume algorithm .. 一些替代方案是例如提到here
  • 哦,here 是一篇退出的“新”短论文,它提供了非常棒的伪代码,您基本上只需要抓取并转换为 Unity c# 代码,甚至获取他们的 entire implementation 并转换它到 c#(但请注意版权)
  • 你可能对this thread也很感兴趣

标签: c# unity3d geometry


【解决方案1】:

嘿,我搜索了一下,发现了一个非常强大的库,它或多或少地提供了您正在寻找的东西,甚至积极支持 Unity 类型:

geometry3Sharp

Unity 项目的实现非常简单

  • 将项目下载为.zip
  • 将文件夹geometry3Sharp-master解压到您的Assets文件夹中
  • 在 Unity 下 ProjectSettings &rightarrow; Player &rightarrow; Other Settings &rightarrow; Configuration &rightarrow; Scripting Define Symbols插入

    G3_USING_UNITY;
    

    正如 README 中所解释的那样:

    geometry3Sharp 支持 Unity 类型的透明转换。要启用此功能,请在 Unity 项目中定义 G3_USING_UNITY,方法是将此字符串添加到 Player Settings 中的 Scripting Define Symbols 框中。

然后,您可以简单地为给定的Vector3 点数组计算定向边界框的边缘,如下所示:

using UnityEngine;
using g3;

public class Example : MonoBehaviour
{
    // Just for the demo I used Transforms so I can simply move them around in the scene
    public Transform[] transforms;

    private void OnDrawGizmos()
    {
        // First wehave to convert the Unity Vector3 array
        // into the g3 type g3.Vector3d
        var points3d = new Vector3d[transforms.Length];
        for (var i = 0; i < transforms.Length; i++)
        {
            // Thanks to the g3 library implictely casted from UnityEngine.Vector3 to g3.Vector3d
            points3d[i] = transforms[i].position;
        }

        // BOOM MAGIC!!!
        var orientedBoundingBox = new ContOrientedBox3(points3d);

        // Now just convert the information back to Unity Vector3 positions and axis
        // Since g3.Vector3d uses doubles but Unity Vector3 uses floats
        // we have to explicitly cast to Vector3
        var center = (Vector3)orientedBoundingBox.Box.Center;

        var axisX = (Vector3)orientedBoundingBox.Box.AxisX;
        var axisY = (Vector3)orientedBoundingBox.Box.AxisY;
        var axisZ = (Vector3)orientedBoundingBox.Box.AxisZ;
        var extends = (Vector3)orientedBoundingBox.Box.Extent;

        // Now we can simply calculate our 8 vertices of the bounding box
        var A = center - extends.z * axisZ - extends.x * axisX - axisY * extends.y;
        var B = center - extends.z * axisZ + extends.x * axisX - axisY * extends.y;
        var C = center - extends.z * axisZ + extends.x * axisX + axisY * extends.y;
        var D = center - extends.z * axisZ - extends.x * axisX + axisY * extends.y;

        var E = center + extends.z * axisZ - extends.x * axisX - axisY * extends.y;
        var F = center + extends.z * axisZ + extends.x * axisX - axisY * extends.y;
        var G = center + extends.z * axisZ + extends.x * axisX + axisY * extends.y;
        var H = center + extends.z * axisZ - extends.x * axisX + axisY * extends.y;

        // And finally visualize it
        Gizmos.DrawLine(A, B);
        Gizmos.DrawLine(B, C);
        Gizmos.DrawLine(C, D);
        Gizmos.DrawLine(D, A);

        Gizmos.DrawLine(E, F);
        Gizmos.DrawLine(F, G);
        Gizmos.DrawLine(G, H);
        Gizmos.DrawLine(H, E);

        Gizmos.DrawLine(A, E);
        Gizmos.DrawLine(B, F);
        Gizmos.DrawLine(D, H);
        Gizmos.DrawLine(C, G);

        // And Here we ca just be amazed ;)
    }
}


当然还有像

这样的东西
orientedBoundingBox.Box.Contains(Vector3d)

用于确定给定点是否在该框内。


只是因为 Ruzihm 问道:

当然你可以稍微改变上面的脚本来简单地使用实际的网格顶点:

public MeshFilter[] meshFilters;

private void OnDrawGizmos()
{
    var vertices = new List<Vector3>();
    foreach (var meshFilter in meshFilters)
    {
        // have to multiply the vertices' positions
        // with the lossyScale and add it to the transform.position 
        vertices.AddRange(meshFilter.sharedMesh.vertices.Select(vertex => meshFilter.transform.position + Vector3.Scale(vertex, meshFilter.transform.lossyScale)));
    }

    var points3d = new Vector3d[vertices.Count];

    for (var i = 0; i < vertices.Count; i++)
    {
        points3d[i] = vertices[i];
    }

    // ...
    // From here the code is the same as above

看起来基本一样

【讨论】:

  • 哦,嘿,太好了。很好的发现
  • 所以为了使这个与球体一起工作,你可以在每个球体的表面上放置一些点,然后从所有球体中取出所有点并将它们放入?
  • @Ruzihm 或者作为近似值,您可以获得每个球体的orientedBoundingBox,然后使用void orientedBoundingBox.Box.Contain(Box3d)(= orientingBoundingBox.Box 的类型),然后将主框重新计算为适合所有这些子框。当然不完美,但我猜应该非常接近。
  • @Ruzihm 刚刚用球体模型测试了它,并且只使用了所有顶点(见更新)。既然你提到了它,我对性能很感兴趣,所以也许以后我会测试一个更复杂的模型。但到目前为止,我印象非常深刻 ^^ 将其用于传递 8 个顶点的 meshCollider 可能对复杂模型有一些非常酷的用例
  • @Ruzihm 请注意,这也没有给出最小的边界框。看看在第二个(球体)插图中,在第二次移动之后,框应该与第一次移动大致相同,只是重新定向,但是如果增长并且结果是球体占据边界框的对角,类似到我自己的答案中第一种方法的结果。
【解决方案2】:

不要想到盒子然后尝试适应它。在这种情况下更容易生成框。所以先点,后框。

  1. 找出相距最远的两个点。

  1. 这将是盒子的两个对角。
  2. 计算相对点的方向/法线。
  3. 计算将指向缺失点的偏移方向。

  1. 生成框的其余点。基本上是您计算的方向,它们与相对点的垂直平行线相交。
  2. 根据顶点生成盒子的面。


我在这里为 2D 进行了说明,但与 3D 的唯一区别是第三个角度、不同的角度偏移以及要处理的更多顶点/面。


编辑:

正如 cmets 中所指出的,这可能不是一个完美的解决方案。如果一个或两个肢体有多个点与相对的肢体接近或等距,但横向分开,此方法将为您提供一个不错的近似 的最小边界框,但不会给出 最小的边界框可能

Ruzihm 的插图:

但是,他的插图有点错误,因为我的解决方案不是轴对齐的。这是它将实现的效果,右侧为红色或橙色,具体取决于选择哪个点作为顶点:


更好/正确的解决方案:

在寻找能够提供数学上完美的最小边界框的解决方案时,我找到了this other (C++) question and it's answers。现在我没有时间通读它并在此处复制 Unity/C# 的解决方案,所以我只会指出它。也许我或其他贡献者可以稍后编辑该副本。

【讨论】:

  • 即使对“相距最远的两点”进行广义解释,这个答案也并非在所有二维情况下都适用:i.imgur.com/rOXMOs4.png
  • @Ruzihm 取决于您是否需要轴对齐框。
  • @Draco18s OP 明确表示他希望它非轴对齐:I also need to find the best possible orientation for the box
  • @Ruzihm 不错的收获!我没有考虑那种情况。谢谢!
  • @Ruzihm 我已经(在某种程度上)修复了答案。请考虑重新评估。您(显然)认为我最初的解决方案是轴对齐的,这有点错误,但我理解您批评的精神。
【解决方案3】:

我能够通过使用 OpenCV for Unity 来解决这个问题。我使用了 minAreaRect 方法,它计算 2D 中点周围的拟合边界框(我首先将它们投影到 X/Z 平面上)。

此方法方便地返回一个矩形,具有中心点、角度和宽度/高度。从这里开始,很容易将其扩展到 3D 盒子。

【讨论】:

  • 您可能想补充一下盒子的底部/顶部必须与 x/z 平面平行的问题,否则,这不能保证最小的盒子并且不是有效的答案(参见this example,x-z 投影圆圈为灰色)。从最小边界矩形的边缘向上投影不一定会导致 3d 空间中的点成为最小 3d 框的一部分。 - 例如,在这种情况下,最佳框的 x 坐标超出了 2d x 坐标
  • 不知道为什么我得到这么多反对票。我看到了 z/x 平面对齐的点,但接受的答案有相反的问题,即 z/x 对齐的框可以旋转,因此它没有可能的最小体积。如果您知道这些点与平面对齐,则拉伸 2D 解决方案效果很好
猜你喜欢
  • 2013-06-03
  • 1970-01-01
  • 2017-07-10
  • 1970-01-01
  • 2012-03-23
  • 1970-01-01
  • 2022-01-02
  • 2021-01-24
  • 2015-03-01
相关资源
最近更新 更多