Bounding box dont follow mesh rotation

Hi,

I try to make a script for showing average bounding box of all mesh inside a prefab with measure.
My problem is when I rotate the mesh. Bounding box didnt follow the mesh and stretch to fit the mesh size.

If anyone have a solution of this problem. Here code for bounding box.

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class BoundingBoxShaderUpdate : MonoBehaviour
{
    public bool scriptEnabled = true; // Toggle for enabling/disabling the script
    public bool useDashedLines = false; // Toggle for using dashed or solid lines for bounding box
    public Color boundingBoxColor = Color.red; // Color for the bounding box
    public float dashedLineGap = 0.1f; // Gap between dashed lines
    public bool showBoundingBox = true;
    public bool showMeasurements = true; // Toggle for showing/hiding measurements
    public bool useMeters = true; // Toggle for using meters or centimeters in measurements
    public float meterToCentimeter = 100f; // Conversion factor from meters to centimeters
    public Font measurementFont; // Font for the measurement label
    public int fontSize = 12; // Font size for the measurement label   

#if UNITY_EDITOR
    [InitializeOnLoadMethod]
    static void StartEditorUpdate()
    {
        EditorApplication.update += EditorUpdate;
    }

    static void EditorUpdate()
    {
        SceneView.RepaintAll();
    }
#endif

    void OnDrawGizmos()
    {
        if (!scriptEnabled) return; // If script is disabled, exit without drawing anything

        if (showBoundingBox)
            DrawBoundingBox();

        if (showMeasurements)
            DrawMeasurements();
    }

    void OnDrawGizmosSelected()
    {
        if (!scriptEnabled) return; // If script is disabled, exit without drawing anything

        if (showBoundingBox)
            DrawBoundingBox();

        if (showMeasurements)
            DrawMeasurements();
    }

    void DrawBoundingBox()
    {
        Renderer[] childRenderers = GetComponentsInChildren<Renderer>();
        if (childRenderers.Length > 0)
        {
            Bounds bounds = new Bounds();
            bool initialized = false;

            foreach (Renderer renderer in childRenderers)
            {
                if (!initialized)
                {
                    bounds = renderer.bounds;
                    initialized = true;
                }
                else
                {
                    bounds.Encapsulate(renderer.bounds);
                }
            }

            Gizmos.color = boundingBoxColor; // Set the color for the bounding box

            if (useDashedLines)
                DrawDashedWireCube(bounds.center, bounds.size); // Draw the bounding box with dashed lines
            else
                DrawSolidWireCube(bounds.center, bounds.size); // Draw the bounding box with solid lines
        }
        else
        {
            Debug.LogWarning("No child objects with Renderer components found.");
        }
    }


    void DrawMeasurements()
    {
        Renderer[] childRenderers = GetComponentsInChildren<Renderer>();
        if (childRenderers.Length > 0)
        {
            Bounds bounds = new Bounds();
            bool initialized = false;

            foreach (Renderer renderer in childRenderers)
            {
                if (!initialized)
                {
                    bounds = renderer.bounds;
                    initialized = true;
                }
                else
                {
                    bounds.Encapsulate(renderer.bounds);
                }
            }

            // Draw measurements for each axis
            if (useMeters)
            {
                DrawAxisMeasurement(bounds.center + new Vector3(0, bounds.size.y / 2, 0), bounds.size.y, "Height", Color.green); // Height (Y-axis) at the top
                DrawAxisMeasurement(bounds.center + new Vector3(bounds.size.x / 2, 0, 0), bounds.size.z, "Length", Color.blue); // Width (X-axis) on the right
                DrawAxisMeasurement(bounds.center + new Vector3(0, 0, bounds.size.z / 2), bounds.size.x, "Width", Color.red); // Length (Z-axis) at the front
            }
            else
            {
                DrawAxisMeasurement(bounds.center + new Vector3(0, bounds.size.y / 2, 0), bounds.size.y * meterToCentimeter, "Height", Color.green); // Height (Y-axis) at the top
                DrawAxisMeasurement(bounds.center + new Vector3(bounds.size.x / 2, 0, 0), bounds.size.z * meterToCentimeter, "Length", Color.blue); // Width (X-axis) on the right
                DrawAxisMeasurement(bounds.center + new Vector3(0, 0, bounds.size.z / 2), bounds.size.x * meterToCentimeter, "Width", Color.red); // Length (Z-axis) at the front
            }
        }
        else
        {
            Debug.LogWarning("No child objects with Renderer components found.");
        }
    }

    void DrawSolidWireCube(Vector3 center, Vector3 size)
    {
        Gizmos.color = boundingBoxColor; // Set the color for the bounding box
        Gizmos.DrawWireCube(center, size);
    }

    void DrawDashedWireCube(Vector3 center, Vector3 size)
    {
        Vector3[] vertices = new Vector3[]
        {
            new Vector3(center.x - size.x / 2, center.y + size.y / 2, center.z + size.z / 2),
            new Vector3(center.x + size.x / 2, center.y + size.y / 2, center.z + size.z / 2),
            new Vector3(center.x + size.x / 2, center.y + size.y / 2, center.z - size.z / 2),
            new Vector3(center.x - size.x / 2, center.y + size.y / 2, center.z - size.z / 2),
            new Vector3(center.x - size.x / 2, center.y - size.y / 2, center.z + size.z / 2),
            new Vector3(center.x + size.x / 2, center.y - size.y / 2, center.z + size.z / 2),
            new Vector3(center.x + size.x / 2, center.y - size.y / 2, center.z - size.z / 2),
            new Vector3(center.x - size.x / 2, center.y - size.y / 2, center.z - size.z / 2)
        };

        for (int i = 0; i < 4; i++)
        {
            DrawDashedLine(vertices[i], vertices[(i + 1) % 4], dashedLineGap); // Top square
            DrawDashedLine(vertices[i + 4], vertices[(i + 1) % 4 + 4], dashedLineGap); // Bottom square
            DrawDashedLine(vertices[i], vertices[i + 4], dashedLineGap); // Connecting lines
        }
    }

    void DrawDashedLine(Vector3 start, Vector3 end, float gap)
    {
        float length = (end - start).magnitude;
        int segments = Mathf.CeilToInt(length / gap);
        Vector3 delta = (end - start) / segments;

        for (int i = 0; i < segments; i += 2)
        {
            Gizmos.DrawLine(start + delta * i, start + delta * (i + 1));
        }
    }

    void DrawAxisMeasurement(Vector3 position, float size, string axis, Color textColor)
    {
        GUIStyle style = new GUIStyle();
        style.normal.textColor = textColor;
        style.font = measurementFont; // Set the font
        style.fontSize = fontSize; // Set the font size
        string measurementText;

        if (useMeters)
        {
            measurementText = $" {axis} = {size:F2} m"; // Display size in meters
        }
        else
        {
            measurementText = $" {axis} = {size:F2} cm"; // Display size in centimeters
        }

        Handles.Label(position, measurementText, style);
    }
}

9748726--1395046--Screenshot 2024-04-04 144229.jpg

Yes, the bounding box is an AABB. An “axis aligned bounding box”. It literally just contains the center and the size. The other properties it has are just different representations of the same data. AABB tests are ultra quick because they are axis aligned, that’s why they are used to determine if something is off screen or potentially visible. Over here I posted one of my MSPaint drawings I made years ago that visualizes how Mesh.bounds and Renderer.bounds work. Note that the example is a deliberately choosen extreme case. If the mesh itself is axis aligned in local space, the renderer bounds would match tightly, like in your own second screenshot which is a perfect fit.

ImM not even sure why you think your visualization of the bounding box should somehow rotate since you generate the vertices in worldspace, axis aligned.

1 Like

Thank you for your response. I will check the link.
If I want to rotate the bounding box, I need to generate the vertice in Localspace?

Sorry, I am designer, not very familiar with script and coding.

What is the actual purpose of that? Just for debugging purposes? Do you actually want to use the bounding box for any game logic? Because the bounding box will always be axis aligned as it’s a feature for performance improvement.

Again note that the bounding box can not be rotated as it’s axis aligned. If you just want to visually show some kind of boundary that encapsulates everything of that prefab, you can indeed calculate a rotated boundin box (not a Bounds value) in local space and transform them into worldspace to draw it. Though this would not have any parallels with Unity’s AABB bounding box.

The main question is what kind of renderers you may have and how precise it should be. Calculating a tight rotating bounding box is actually a really hard problem as it’s difficult to figure out what the major axis are so that the volume is at a minimum. This is even a hard problem in 2d. More generally calculating the minimum convex hull of some arbitrary points in space is a quite difficult problem that doesn’t have an easy solution. Calculating something like the bounding sphere is a bit easier and depending on the usecase often times enough. Of course when you specifically want a precise visual representation, there’s no real way around going through all vertices of all renderers and calcluating the minimum box yourself. Calculating the local AABB of the prefab parent is kinda trivial as you just have to find the min and max values in each axis and you’re done. That’s why axis aligned boxes are great ^^

So please clarify what the actual goal is. You just want an in editor box around your object that isn’t axis aligned, just to visually group them? Because you do know that the Gizmos are a pure editor feature. Nothing will be displayed at runtime in a build game.

2 Likes

Thank you for all this details.
The main purpose of this script. Its for game mode, when selecting object show mesh or prefab dimension with bounding box around the mesh or similar effect. Only visible if we select the object.
For final purpose its similar of MRTK3 bound design for concept.
This feature have to be visible after building the application.

Just make your own graphics to represent this.

That will give you full control over appearance and what it does when the object in question is modified.