繁体   English   中英

在 Unity 中为一组 3D 点查找定向边界框

[英]Finding an oriented bounding box for a set of 3D points in Unity

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

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

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

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

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

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

到目前为止,我只尝试了基本方法,即不旋转封装盒以获得最佳配合。 结果真的离我需要的很远。

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

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?

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

几何3锋利

在 Unity 项目中的实现非常简单

  • 将项目下载为.zip
  • geometry3Sharp-master文件夹解压到你的Assets文件夹中
  • 在 Unity 下ProjectSettingsPlayerOther SettingsConfigurationScripting Define Symbols insert

     G3_USING_UNITY;

    正如自述文件中所解释的那样:

    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

看起来基本相同

在此处输入图像描述

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

  1. 找到彼此最远的两个点。

在此处输入图像描述

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

在此处输入图像描述

  1. 生成方框点的rest。 基本上,您通过它们与相对点的垂直平行线相交的位置计算出的方向。
  2. 根据顶点生成盒子的面。

在此处输入图像描述


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


编辑:

正如评论中所指出的,这可能不是一个完美的解决方案。 如果一个或两个末端具有多个点,这些点与相对末端接近或距离相等,但横向分开,此方法将为您提供最小边界框适当近似值,但不会给出最小边界盒子可能

Ruzihm的插图:

在此处输入图像描述

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

在此处输入图像描述


更好/正确的解决方案:

在寻找可以提供数学上完美的最小边界框的解决方案时,我发现了另一个 (C++) 问题及其答案 现在我没有时间通读它并在这里为 Unity/C# 复制解决方案,所以我只会指出它。 也许我或其他贡献者可以稍后编辑该副本。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM