简体   繁体   English

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

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

I have a set of 3D points, or in fact small spheres, that I need to enclose with the smallest possible 3D box using Unity 3D.我有一组 3D 点,或者实际上是小球体,我需要使用 Unity 3D 用尽可能小的 3D 框将其包围。

In the case where the enclosing box can only be moved and scaled, the solution is quite trivial, you simply iterate over all the points and encapsulate each one.在封闭框只能移动和缩放的情况下,解决方案非常简单,您只需遍历所有点并封装每个点。 But I also need to find the best possible orientation for the box.但我还需要为盒子找到最佳的方向。

So, to illustrate the problem in ASCII, given a basic 2D scenario with only two points:因此,为了用 ASCII 说明问题,给定一个只有两点的基本 2D 场景:

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

Using a regular growing bounding box, you will end up with a very large enclosing box containing mostly blank space, while in this case I need a box that is very thin and rotated about 45 degrees around the Z axis.使用常规增长的边界框,您最终会得到一个包含大部分空白空间的非常大的封闭框,而在这种情况下,我需要一个非常薄且围绕 Z 轴旋转约 45 度的框。 Basically just a line connecting the two points.基本上只是一条连接两点的线。

I never know how many points that needs to be grouped together though.我永远不知道有多少点需要组合在一起。 And as mentioned, it has to work in 3D.如前所述,它必须在 3D 中工作。

So far I only tried the basic approach where I do not rotate the encapsulating box for the best fit.到目前为止,我只尝试了基本方法,即不旋转封装盒以获得最佳配合。 The result is really way off what I need.结果真的离我需要的很远。

I was considering a brute force method, based on genetic algorithms, where I generate huge amounts of random boxes and simply select the one with the lowest area while still containing all the points.我正在考虑一种基于遗传算法的蛮力方法,在该方法中我生成大量随机框,并简单地 select 面积最小但仍包含所有点的那个。 However, that is too slow.但是,这太慢了。

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?

Hey I searched around a bit and I found a pretty powerful library which offers what you are looking for more or less straight forward and even actively supports Unity types:嘿,我搜索了一下,我发现了一个非常强大的库,它或多或少地提供了你正在寻找的东西,甚至积极支持 Unity 类型:

geometry3Sharp几何3锋利

The implementation into a Unity Project is as simple as在 Unity 项目中的实现非常简单

  • Download the project as .zip将项目下载为.zip
  • unpack the folder geometry3Sharp-master into your Assets foldergeometry3Sharp-master文件夹解压到你的Assets文件夹中
  • In Unity under ProjectSettingsPlayerOther SettingsConfigurationScripting Define Symbols insert在 Unity 下ProjectSettingsPlayerOther SettingsConfigurationScripting Define Symbols insert

     G3_USING_UNITY;

    Just as also explained in the README:正如自述文件中所解释的那样:

    geometry3Sharp supports transparent conversion with Unity types. geometry3Sharp支持 Unity 类型的透明转换。 To enable this, define G3_USING_UNITY in your Unity project, by adding this string to the Scripting Define Symbols box in the Player Settings.要启用此功能,请在 Unity 项目中定义G3_USING_UNITY ,方法是将此字符串添加到 Player Settings 中的 Scripting Define Symbols 框中。

Then you can simply calculate the edges of an oriented bounding box for a given array of Vector3 points like this:然后,您可以简单地计算给定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 ;)
    }
}

在此处输入图像描述


And of course there is stuff like当然还有像

orientedBoundingBox.Box.Contains(Vector3d)

for determine if a given point lies within that box.用于确定给定点是否位于该框内。


Just because Ruzihm asked:只是因为 Ruzihm 问道:

Ofcourse you can slightly change the upper script to simply use actual mesh vertices:当然,您可以稍微更改上面的脚本以简单地使用实际的网格顶点:

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

Which looks basically the same看起来基本相同

在此处输入图像描述

Don't think of the box and then try to adapt it.不要想到盒子然后尝试适应它。 It's easier to generate the box in this case.在这种情况下更容易生成框。 So points first, box later.所以先点,后框。

  1. Find the two points most distant from eachother.找到彼此最远的两个点。

在此处输入图像描述

  1. These will be two opposite corners of the box.这些将是盒子的两个对角。
  2. Calculate the direction/normal to the opposite point.计算相对点的方向/法线。
  3. Calculate offset directions from that that will point to the missing points.计算将指向缺失点的偏移方向。

在此处输入图像描述

  1. Generate the rest of the points of the box.生成方框点的rest。 Basically the directions you calculated by where they intersect the perpendicular paralel of the opposite point.基本上,您通过它们与相对点的垂直平行线相交的位置计算出的方向。
  2. Generate the faces of the box based on its vertices.根据顶点生成盒子的面。

在此处输入图像描述


I've illustrated for 2D here, but the only difference to 3D is a third angle, different angle offsets, and more vertices/faces to deal with.我在这里为 2D 进行了说明,但与 3D 的唯一区别是第三个角度、不同的角度偏移以及要处理的更多顶点/面。


EDIT:编辑:

As pointed out in the comments, this might not be a perfect solution.正如评论中所指出的,这可能不是一个完美的解决方案。 In cases where one or both extremities have multiple points that are close to, or at equal distance, from the opposing extremity, but separated laterally, this method will give you a decent approximation of the smallest bounding box, but will not give the smallest boudning box possible .如果一个或两个末端具有多个点,这些点与相对末端接近或距离相等,但横向分开,此方法将为您提供最小边界框适当近似值,但不会给出最小边界盒子可能

Ruzihm's illustration: Ruzihm的插图:

在此处输入图像描述

However, his illustration is a bit wrong, in that my solution is not axis-aligned.但是,他的插图有点错误,因为我的解决方案不是轴对齐的。 Here is what it would achieve, on the right, in red or orange, depending on which point is picked as the vertex:这是它将在右侧以红色或橙色实现的效果,具体取决于选择哪个点作为顶点:

在此处输入图像描述


Better/Correct solution:更好/正确的解决方案:

While looking for a solution that would give the mathematically perfect minimum bounding box, I've found this other (C++) question and it's answers .在寻找可以提供数学上完美的最小边界框的解决方案时,我发现了另一个 (C++) 问题及其答案 Right now I don't have the time to read through it all and replicate the solution here for Unity/C#, so I will only point to it.现在我没有时间通读它并在这里为 Unity/C# 复制解决方案,所以我只会指出它。 Maybe I or another contributor can edit-in a replication of that later.也许我或其他贡献者可以稍后编辑该副本。

I was able to solve this by using OpenCV for Unity.我能够通过将 OpenCV 用于 Unity 来解决这个问题。 I used the minAreaRect method, which calculates a fit bounding-box around the points in 2D (I projected them onto the X/Z plane first).我使用了 minAreaRect 方法,它计算 2D 中点周围的拟合边界框(我首先将它们投影到 X/Z 平面上)。

This method conveniently returned a rectangle, with a center point, angle and width / height.此方法方便地返回一个矩形,具有中心点、角度和宽度/高度。 From here on it was quite easy to extend this to a 3D box.从这里开始,很容易将其扩展到 3D 盒子。

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

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