MRが楽しい

MRやVRについて学習したことを書き残す

Unityで複数のゲームオブジェクトのメッシュ形状に合わせてBoundsを設定する

本日は Unity の小ネタ枠です。
Unity で複数のゲームオブジェクトのメッシュ形状に合わせて Bounds を設定する方法を記事にします。
f:id:bluebirdofoz:20210112231910j:plain

Boundsの組合せ

複数の Bounds を組み合わせるには Bounds クラスの Encapsulate 関数を使います。
docs.unity3d.com

サンプルシーン

以下のような様々な大きさや形状の3Dモデルを子オブジェクトに持つオブジェクトを作成しました。
f:id:bluebirdofoz:20210112231920j:plain

コード例

複数のゲームオブジェクトのメッシュ形状から Bounds を計算するサンプルコードを作成しました。
以下の処理を行います。
1.アタッチしたゲームオブジェクトの子オブジェクトを全てチェックする。
2.MeshFilter が設定されているオブジェクトからメッシュ形状の Bounds を取得する
3.取得したメッシュ形状の Bounds 全てを含むワールド座標系の Bounds を取得する
4.アタッチしたゲームオブジェクトのローカル座標系に Bounds を変換する
5.シーンで結果が確認できるように Bounds サイズの BoxCollider を設定する
・ChildObjBounds.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChildObjBounds : MonoBehaviour
{
    /// <summary>
    /// 子オブジェクトの統合Bounds
    /// </summary>
    public Bounds childObjBounds;
    
    void Start()
    {
        // 
        childObjBounds = CalcLocalObjBounds(this.gameObject);

        // Boundsの大きさと形状が見た目に分かるようコライダーを追加する
        BoxCollider collider = this.gameObject.AddComponent<BoxCollider>();
        // 計算されたバウンドボックスに合わせてコライダーの大きさと位置を変更する
        collider.center = childObjBounds.center;
        collider.size = childObjBounds.size;
    }

    /// <summary>
    /// 現在オブジェクトのローカル座標でのバウンド計算
    /// </summary>
    private Bounds CalcLocalObjBounds(GameObject obj)
    {
        // 指定オブジェクトのワールドバウンドを計算する
        Bounds totalBounds = CalcChildObjWorldBounds(obj, new Bounds());

        // ローカルオブジェクトの相対座標に合わせてバウンドを再計算する
        // オブジェクトのワールド座標とサイズを取得する
        Vector3 ObjWorldPosition = this.transform.position;
        Vector3 ObjWorldScale = this.transform.lossyScale;

        // バウンドのローカル座標とサイズを取得する
        Vector3 totalBoundsLocalCenter = new Vector3(
            (totalBounds.center.x - ObjWorldPosition.x) / ObjWorldScale.x,
            (totalBounds.center.y - ObjWorldPosition.y) / ObjWorldScale.y,
            (totalBounds.center.z - ObjWorldPosition.z) / ObjWorldScale.z);
        Vector3 meshBoundsLocalSize = new Vector3(
            totalBounds.size.x / ObjWorldScale.x,
            totalBounds.size.y / ObjWorldScale.y,
            totalBounds.size.z / ObjWorldScale.z);

        Bounds localBounds = new Bounds(totalBoundsLocalCenter, meshBoundsLocalSize);

        return localBounds;
    }

    /// <summary>
    /// 子オブジェクトのワールド座標でのバウンド計算(再帰処理)
    /// </summary>
    private Bounds CalcChildObjWorldBounds(GameObject obj, Bounds bounds)
    {
        // 指定オブジェクトの全ての子オブジェクトをチェックする
        foreach (Transform child in obj.transform)
        {
            // メッシュフィルターの存在確認
            MeshFilter filter = child.gameObject.GetComponent<MeshFilter>();

            if (filter != null)
            {
                // オブジェクトのワールド座標とサイズを取得する
                Vector3 ObjWorldPosition = child.position;
                Vector3 ObjWorldScale = child.lossyScale;

                // フィルターのメッシュ情報からバウンドボックスを取得する
                Bounds meshBounds = filter.mesh.bounds;

                // バウンドのワールド座標とサイズを取得する
                Vector3 meshBoundsWorldCenter = meshBounds.center + ObjWorldPosition;
                Vector3 meshBoundsWorldSize = Vector3.Scale(meshBounds.size, ObjWorldScale);

                // バウンドの最小座標と最大座標を取得する
                Vector3 meshBoundsWorldMin = meshBoundsWorldCenter - (meshBoundsWorldSize / 2);
                Vector3 meshBoundsWorldMax = meshBoundsWorldCenter + (meshBoundsWorldSize / 2);

                // 取得した最小座標と最大座標を含むように拡大/縮小を行う
                if (bounds.size == Vector3.zero)
                {
                    // 元バウンドのサイズがゼロの場合はバウンドを作り直す
                    bounds = new Bounds(meshBoundsWorldCenter, Vector3.zero);
                }
                bounds.Encapsulate(meshBoundsWorldMin);
                bounds.Encapsulate(meshBoundsWorldMax);
            }

            // 再帰処理
            bounds = CalcChildObjWorldBounds(child.gameObject, bounds);
        }
        return bounds;
    }
}

動作確認

スクリプトを子オブジェクトに持つオブジェクトに追加します。
f:id:bluebirdofoz:20210112231936j:plain

シーンを再生すると、子オブジェクトのメッシュ形状を内包する BoxCollider がオブジェクトに追加されます。
f:id:bluebirdofoz:20210112231950j:plain
f:id:bluebirdofoz:20210112232004j:plain
f:id:bluebirdofoz:20210112232014j:plain