Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Finding a 3D objects 2D bounds

Discussion in 'Scripting' started by Stevil, Feb 28, 2014.

  1. Stevil

    Stevil

    Joined:
    Dec 9, 2012
    Posts:
    14
    Hey all,

    I'm just playing around with a few things at the moment for the sake of learning, and I'm struggling to find a good solution for finding the 2D position / bounds of a 3D object. Lets say for example I am making an RTS and want to show selected troops by drawing a bounding box around them I'd need to know the 2D position on the screen and then the width and height on the screen to draw the box around it. Or another example might be I am doing a hack and slash game and want to have a health bar directly above all of the ememy's heads. Another example might be I want to put a chat bubble directly above their head in an RPG, or maybe something like damage text showing above peoples heads each time they get hit... I'm sure you get what I mean by now though.

    Now I understand you can use WorldToScreen point to find the screen, and you can then use the bounds to get the width of the objects and then work out from the center there to find each of 8 bounding points and use min and max and then find what in theory would be the bounding box, but the issue I get with this is when I start rotating the object it starts to skew the size of the bounding box. You can see what I mean in this image.

    $rotation-example.jpg

    The code I'm using for that is based on an example I found, which you can see here

    Code (csharp):
    1.             Bounds b = renderer.bounds;
    2.             Camera cam = Camera.main;
    3.             Vector3[] pts = new Vector3[8];
    4.             float margin = 0f;
    5.            
    6.             //The object is behind us
    7.             if (cam.WorldToScreenPoint (b.center).z < 0) return;
    8.            
    9.             //All 8 vertices of the bounds
    10.             pts[0] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z));
    11.             pts[1] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z - b.extents.z));
    12.             pts[2] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y - b.extents.y, b.center.z + b.extents.z));
    13.             pts[3] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y - b.extents.y, b.center.z - b.extents.z));
    14.             pts[4] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z));
    15.             pts[5] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y + b.extents.y, b.center.z - b.extents.z));
    16.             pts[6] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y - b.extents.y, b.center.z + b.extents.z));
    17.             pts[7] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y - b.extents.y, b.center.z - b.extents.z));
    18.            
    19.             //Get them in GUI space
    20.             for (int i=0;i<pts.Length;i++) pts[i].y = Screen.height-pts[i].y;
    21.            
    22.             //Calculate the min and max positions
    23.             Vector3 min = pts[0];
    24.             Vector3 max = pts[0];
    25.             for (int i=1;i<pts.Length;i++) {
    26.                 min = Vector3.Min (min, pts[i]);
    27.                 max = Vector3.Max (max, pts[i]);
    28.             }
    29.            
    30.             //Construct a rect of the min and max positions and apply some margin
    31.             Rect r = Rect.MinMaxRect (min.x,min.y,max.x,max.y);
    32.             r.xMin -= margin;
    33.             r.xMax += margin;
    34.             r.yMin -= margin;
    35.             r.yMax += margin;
    36.            
    37.             //Render the box
    38.             GUI.Box (r,"");
    39.  
    While this works, it doesn't seem to be very accurate and if I wanted to use it I could see a few situations where being this out could definitely cause issues or just wouldn't be ideal. Now I'm not sure if I'm doing something wrong in my code or if this is just how it works, or if there is a better way to attempt this?

    I know in theory I could probably achieve all the examples I mentioned previously doing things in 3D space and just rotating them to face the camera constantly, but I'd much rather find out if there is a better more accurate way to achieve this. All my google / forum searching has given me similar to what I posted above, but I'm sure there would have to be a better way to do it.

    Thanks in advance!
     
  2. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    Your explanation on how to solve this is correct and I think it will work.

    I think your problem lies with Vector3.Min and Vector3.Max. Vector3.Min returns the smallest vector and not the smallest x and y value. This means the smallest x value can be in a different vector than the smallest y value.

    To overcome this problem try handling the x- and y-axis separately. Find the smallest and largest x value first, and then the smallest and largest y value. You can do this with Mathf.Min and Math.Max.

    Let me know if you fixed it. :)

    EDIT: Sorry, but I am wrong. :( Vector.Min and Vector.Max should work fine.
     
    Last edited: Feb 28, 2014
  3. booiljoung

    booiljoung

    Joined:
    May 12, 2013
    Posts:
    57
  4. booiljoung

    booiljoung

    Joined:
    May 12, 2013
    Posts:
    57
    2nd way.

    Make a prefab (game object) contain a BoxCollider as trigger.
    Scale size of BoxCollier you want.
    Put this game object centre of dragging area.
    wait a few physics delta-time.
    this object will receive OnTrigger message with solder's collider.
     
  5. Stevil

    Stevil

    Joined:
    Dec 9, 2012
    Posts:
    14
    Thanks for the advice, I tried changing it over and it made no difference. My altered code replaced the loop through the points with this

    Code (csharp):
    1.             float minX = pts[0].x;
    2.             float maxX = pts[0].x;
    3.             float minY = pts[0].y;
    4.             float maxY = pts[0].y;
    5.             for (int i=1;i<pts.Length;i++) {
    6.                 minX = Mathf.Min( pts[ i ].x, minX );
    7.                 maxX = Mathf.Max( pts[ i ].x, maxX );
    8.                 minY = Mathf.Min( pts[ i ].y, minY );
    9.                 maxY = Mathf.Max( pts[ i ].y, maxY );
    10.             }
    11.             Rect r = new Rect( minX, minY, maxX - minX, maxY - minY );
    12.  
    What I did for debugging was actually placed markers on each of points to try and see what is happening, interestingly enough the points are going out wider than the object as can be seen in the image below

    $points.jpg

    It's interesting to see that there are points pushing it out wider, but not sure what's making the top go up as high as what it is. I have a feeling I am missing something here...
     
  6. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    Yeah I know i was wrong and edited my reply. Next time I will make a new reply instead.

    The bounding box is bound to world coordinates. Doing Vector3.Min and Vector.Max before convert them into GUI space should work.
     
  7. Stevil

    Stevil

    Joined:
    Dec 9, 2012
    Posts:
    14
    It looks as though it's the 3D calculations rather than the conversion to 2D, I added in a cube to be displayed on one of the bounds purely for testing. In the image below you can see it's positioning the of the 2D object does line up with the 3D object, but the 3D object is a fair distance away from cube itself. This at least has cut down where the possible issues are

    $point-in-3d.jpg

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    4.  
    5. public class unit : MonoBehaviour {
    6.     public bool selected = false;
    7.     public Texture2D outline = null;
    8.     private GameObject cube = null;
    9.  
    10.     // Use this for initialization
    11.     void Start () {
    12.         cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    13.         cube.transform.localScale = new Vector3 (0.3f, 0.3f, 0.3f);
    14.     }
    15.  
    16.     void OnGUI(){
    17.         if (true) {
    18.             Bounds b = renderer.bounds;
    19.             cube.transform.position = new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z);
    20.             Vector3 pts = HandleUtility.WorldToGUIPoint (new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z));
    21.  
    22.             GUI.Box (new Rect(pts.x-5, pts.y -5, 10,10 ), "" );
    23.         }
    24.     }
    25. }
    Thanks for the help so far, I'll keep looking into it and post my findings for anyone else who might be having problems. Still open to ideas if anyone has any :)
     
  8. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    This code calculates the rectangle out from the objects vertices and it works. You should never use this code on a high poly object.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Test : MonoBehaviour {
    6.  
    7.     public float margin = 10f;
    8.     public GameObject go;
    9.     private Vector3[] v;
    10.  
    11.     Camera cam;
    12.  
    13.     void Start()
    14.     {
    15.         cam = Camera.main;
    16.         v = go.GetComponent<MeshFilter>().mesh.vertices;
    17.     }
    18.  
    19.  
    20.     void OnGUI()
    21.     {
    22.         //The object is behind us
    23.         if (cam.WorldToScreenPoint (go.renderer.bounds.center).z < 0) return;
    24.  
    25.         v = go.GetComponent<MeshFilter>().mesh.vertices;
    26.  
    27.         for(int i = 0; i < v.Length; i++)
    28.         {
    29.             //World space
    30.             v[i] = go.transform.TransformPoint(v[i]);
    31.             //GUI space
    32.             v[i] = cam.WorldToScreenPoint(v[i]);
    33.             v[i].y = Screen.height - v[i].y;
    34.         }
    35.  
    36.         Vector3 min = v[0];
    37.         Vector3 max = v[0];
    38.        
    39.         for (int i = 1; i < v.Length; i++) {
    40.            
    41.             min = Vector3.Min (min, v[i]);
    42.             max = Vector3.Max (max, v[i]);
    43.         }
    44.        
    45.         //Construct a rect of the min and max positions and apply some margin
    46.         Rect r = Rect.MinMaxRect (min.x,min.y,max.x,max.y);
    47.        
    48.         r.xMin -= margin;
    49.         r.xMax += margin;
    50.         r.yMin -= margin;
    51.         r.yMax += margin;      
    52.        
    53.         //Render the box
    54.         GUI.Box (r,"");
    55.     }
    56. }
    57.  
     
unityunity