Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Bounding box dont follow mesh rotation

Discussion in 'Scripting' started by prothree_premium, Apr 4, 2024.

  1. prothree_premium

    prothree_premium

    Joined:
    May 7, 2020
    Posts:
    3
    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.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. #if UNITY_EDITOR
    4. using UnityEditor;
    5. #endif
    6.  
    7. [ExecuteAlways]
    8. public class BoundingBoxShaderUpdate : MonoBehaviour
    9. {
    10.     public bool scriptEnabled = true; // Toggle for enabling/disabling the script
    11.     public bool useDashedLines = false; // Toggle for using dashed or solid lines for bounding box
    12.     public Color boundingBoxColor = Color.red; // Color for the bounding box
    13.     public float dashedLineGap = 0.1f; // Gap between dashed lines
    14.     public bool showBoundingBox = true;
    15.     public bool showMeasurements = true; // Toggle for showing/hiding measurements
    16.     public bool useMeters = true; // Toggle for using meters or centimeters in measurements
    17.     public float meterToCentimeter = 100f; // Conversion factor from meters to centimeters
    18.     public Font measurementFont; // Font for the measurement label
    19.     public int fontSize = 12; // Font size for the measurement label  
    20.  
    21. #if UNITY_EDITOR
    22.     [InitializeOnLoadMethod]
    23.     static void StartEditorUpdate()
    24.     {
    25.         EditorApplication.update += EditorUpdate;
    26.     }
    27.  
    28.     static void EditorUpdate()
    29.     {
    30.         SceneView.RepaintAll();
    31.     }
    32. #endif
    33.  
    34.     void OnDrawGizmos()
    35.     {
    36.         if (!scriptEnabled) return; // If script is disabled, exit without drawing anything
    37.  
    38.         if (showBoundingBox)
    39.             DrawBoundingBox();
    40.  
    41.         if (showMeasurements)
    42.             DrawMeasurements();
    43.     }
    44.  
    45.     void OnDrawGizmosSelected()
    46.     {
    47.         if (!scriptEnabled) return; // If script is disabled, exit without drawing anything
    48.  
    49.         if (showBoundingBox)
    50.             DrawBoundingBox();
    51.  
    52.         if (showMeasurements)
    53.             DrawMeasurements();
    54.     }
    55.  
    56.     void DrawBoundingBox()
    57.     {
    58.         Renderer[] childRenderers = GetComponentsInChildren<Renderer>();
    59.         if (childRenderers.Length > 0)
    60.         {
    61.             Bounds bounds = new Bounds();
    62.             bool initialized = false;
    63.  
    64.             foreach (Renderer renderer in childRenderers)
    65.             {
    66.                 if (!initialized)
    67.                 {
    68.                     bounds = renderer.bounds;
    69.                     initialized = true;
    70.                 }
    71.                 else
    72.                 {
    73.                     bounds.Encapsulate(renderer.bounds);
    74.                 }
    75.             }
    76.  
    77.             Gizmos.color = boundingBoxColor; // Set the color for the bounding box
    78.  
    79.             if (useDashedLines)
    80.                 DrawDashedWireCube(bounds.center, bounds.size); // Draw the bounding box with dashed lines
    81.             else
    82.                 DrawSolidWireCube(bounds.center, bounds.size); // Draw the bounding box with solid lines
    83.         }
    84.         else
    85.         {
    86.             Debug.LogWarning("No child objects with Renderer components found.");
    87.         }
    88.     }
    89.  
    90.  
    91.     void DrawMeasurements()
    92.     {
    93.         Renderer[] childRenderers = GetComponentsInChildren<Renderer>();
    94.         if (childRenderers.Length > 0)
    95.         {
    96.             Bounds bounds = new Bounds();
    97.             bool initialized = false;
    98.  
    99.             foreach (Renderer renderer in childRenderers)
    100.             {
    101.                 if (!initialized)
    102.                 {
    103.                     bounds = renderer.bounds;
    104.                     initialized = true;
    105.                 }
    106.                 else
    107.                 {
    108.                     bounds.Encapsulate(renderer.bounds);
    109.                 }
    110.             }
    111.  
    112.             // Draw measurements for each axis
    113.             if (useMeters)
    114.             {
    115.                 DrawAxisMeasurement(bounds.center + new Vector3(0, bounds.size.y / 2, 0), bounds.size.y, "Height", Color.green); // Height (Y-axis) at the top
    116.                 DrawAxisMeasurement(bounds.center + new Vector3(bounds.size.x / 2, 0, 0), bounds.size.z, "Length", Color.blue); // Width (X-axis) on the right
    117.                 DrawAxisMeasurement(bounds.center + new Vector3(0, 0, bounds.size.z / 2), bounds.size.x, "Width", Color.red); // Length (Z-axis) at the front
    118.             }
    119.             else
    120.             {
    121.                 DrawAxisMeasurement(bounds.center + new Vector3(0, bounds.size.y / 2, 0), bounds.size.y * meterToCentimeter, "Height", Color.green); // Height (Y-axis) at the top
    122.                 DrawAxisMeasurement(bounds.center + new Vector3(bounds.size.x / 2, 0, 0), bounds.size.z * meterToCentimeter, "Length", Color.blue); // Width (X-axis) on the right
    123.                 DrawAxisMeasurement(bounds.center + new Vector3(0, 0, bounds.size.z / 2), bounds.size.x * meterToCentimeter, "Width", Color.red); // Length (Z-axis) at the front
    124.             }
    125.         }
    126.         else
    127.         {
    128.             Debug.LogWarning("No child objects with Renderer components found.");
    129.         }
    130.     }
    131.  
    132.     void DrawSolidWireCube(Vector3 center, Vector3 size)
    133.     {
    134.         Gizmos.color = boundingBoxColor; // Set the color for the bounding box
    135.         Gizmos.DrawWireCube(center, size);
    136.     }
    137.  
    138.     void DrawDashedWireCube(Vector3 center, Vector3 size)
    139.     {
    140.         Vector3[] vertices = new Vector3[]
    141.         {
    142.             new Vector3(center.x - size.x / 2, center.y + size.y / 2, center.z + size.z / 2),
    143.             new Vector3(center.x + size.x / 2, center.y + size.y / 2, center.z + size.z / 2),
    144.             new Vector3(center.x + size.x / 2, center.y + size.y / 2, center.z - size.z / 2),
    145.             new Vector3(center.x - size.x / 2, center.y + size.y / 2, center.z - size.z / 2),
    146.             new Vector3(center.x - size.x / 2, center.y - size.y / 2, center.z + size.z / 2),
    147.             new Vector3(center.x + size.x / 2, center.y - size.y / 2, center.z + size.z / 2),
    148.             new Vector3(center.x + size.x / 2, center.y - size.y / 2, center.z - size.z / 2),
    149.             new Vector3(center.x - size.x / 2, center.y - size.y / 2, center.z - size.z / 2)
    150.         };
    151.  
    152.         for (int i = 0; i < 4; i++)
    153.         {
    154.             DrawDashedLine(vertices[i], vertices[(i + 1) % 4], dashedLineGap); // Top square
    155.             DrawDashedLine(vertices[i + 4], vertices[(i + 1) % 4 + 4], dashedLineGap); // Bottom square
    156.             DrawDashedLine(vertices[i], vertices[i + 4], dashedLineGap); // Connecting lines
    157.         }
    158.     }
    159.  
    160.     void DrawDashedLine(Vector3 start, Vector3 end, float gap)
    161.     {
    162.         float length = (end - start).magnitude;
    163.         int segments = Mathf.CeilToInt(length / gap);
    164.         Vector3 delta = (end - start) / segments;
    165.  
    166.         for (int i = 0; i < segments; i += 2)
    167.         {
    168.             Gizmos.DrawLine(start + delta * i, start + delta * (i + 1));
    169.         }
    170.     }
    171.  
    172.     void DrawAxisMeasurement(Vector3 position, float size, string axis, Color textColor)
    173.     {
    174.         GUIStyle style = new GUIStyle();
    175.         style.normal.textColor = textColor;
    176.         style.font = measurementFont; // Set the font
    177.         style.fontSize = fontSize; // Set the font size
    178.         string measurementText;
    179.  
    180.         if (useMeters)
    181.         {
    182.             measurementText = $" {axis} = {size:F2} m"; // Display size in meters
    183.         }
    184.         else
    185.         {
    186.             measurementText = $" {axis} = {size:F2} cm"; // Display size in centimeters
    187.         }
    188.  
    189.         Handles.Label(position, measurementText, style);
    190.     }
    191. }
    192.  
     

    Attached Files:

  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,124
    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.
     
    halley likes this.
  3. prothree_premium

    prothree_premium

    Joined:
    May 7, 2020
    Posts:
    3
    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.
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,124
    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.
     
    zulo3d and MelvMay like this.
  5. prothree_premium

    prothree_premium

    Joined:
    May 7, 2020
    Posts:
    3
    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.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,079
    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.