簡體   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