3

I have a game object that lives in world space in my scene. I would like to get the coordinates of the corners of the bounding rectangle for this game object's renderer in screen space because I have UI elements that want to be positioned around this box.

Context: I'm making a tutorial and I am using panels to darken everything except for a game object that will be left un-darkened. I can do this easily with buttons that already live in screen space and have rect transforms on them, but I can't figure out how to do this around a game object in world space. We are using a camera with orthographic projection and are using Unity version 2019.2.17f1.

Here's what I've tried:

public void FocusOnRenderer(Renderer renderer) {

        // left, top, right, and bottom are Panels whose pivots are set as follows:
        // top: (1, 0)
        // right: (0, 0)
        // bottom: (0, 1)
        // left: (1, 1)
        // so when their positions are set to be the corners of the target bounding box, they will fit together nicely.

        left.gameObject.SetActive(true);
        top.gameObject.SetActive(true);
        right.gameObject.SetActive(true);
        bottom.gameObject.SetActive(true);

        Vector3 center = HandleUtility.WorldToGUIPoint(renderer.bounds.center); // center of bounding box
        Vector3 halfSize = HandleUtility.WorldToGUIPoint(renderer.bounds.extents)); // half size of bounding box

        Vector3 topRight = center + halfSize;
        Vector3 topLeft = center  + new Vector3(-halfSize.x, halfSize.y, halfSize.z);
        Vector3 bottomRight = center  + new Vector3(halfSize.x, -halfSize.y, halfSize.z);
        Vector3 bottomLeft = center  + new Vector3(-halfSize.x, -halfSize.y, halfSize.z);

        left.position = topLeft;
        top.position = topRight;
        right.position = bottomRight;
        bottom.position = bottomLeft;
    }

I think this is wrong because what I'm doing with the renderer's bounds to compute halfSize and center aren't giving me a bounding rectangle. I was hoping there would be an easy built in way to do this but I haven't been able to find anything so far.

Thank you for your help!

emsha
  • 73
  • 1
  • 6
  • Have you tried using Vector2 instead of Vector3? WorldToGUIPoint() returns a Vector2 so something may be happening during the type conversion – NicknEma Dec 23 '20 at 11:04
  • @Kirlian in that case all that's happening is that the `z` component will be `0` – derHugo Dec 23 '20 at 11:33
  • @derHugo and isn't that what we want, since we're talking about UI? And they will be `0` anyways, since `halfSize` comes from a Vector2 method and it will have its own z set to `0`. – NicknEma Dec 23 '20 at 15:56
  • @Kirlian well, yes ... I just referred to your comment `something may be happening during the type conversion` -> No, nothing special happens during the type conversion really except that `z` will be `0` .. it's something to have in mind not something that actually "happens" ;) – derHugo Dec 23 '20 at 15:59
  • @derHugo oh sorry, then I misunderstood – NicknEma Dec 23 '20 at 19:33

3 Answers3

2

I found an answer (with video suggestion from @SparrowsNest on the Unity Forum)! Here's the video starting at the relevant timestamp: https://youtu.be/2Tgqr1_ajqE?t=1061

Steps:

  1. get corners of bounding box from the renderer's bounds
  2. convert those corners into screen space
  3. get the min and max x and y values
  4. set my panels' positions using those min and max x and y values

Here's my code:

    public void FocusOnBounds(Bounds bounds) {
        
        // left, top, right, and bottom are Panels whose pivots are set as follows:
        // top: (1, 0)
        // right: (0, 0)
        // bottom: (0, 1)
        // left: (1, 1)
        // so when their positions are set to be the corners of the target bounding box, they will fit together nicely.

        left.gameObject.SetActive(true);
        top.gameObject.SetActive(true);
        right.gameObject.SetActive(true);
        bottom.gameObject.SetActive(true);

        Vector3 c = bounds.center;
        Vector3 e = bounds.extents;

        Vector3[] worldCorners = new [] {
            new Vector3( c.x + e.x, c.y + e.y, c.z + e.z ),
            new Vector3( c.x + e.x, c.y + e.y, c.z - e.z ),
            new Vector3( c.x + e.x, c.y - e.y, c.z + e.z ),
            new Vector3( c.x + e.x, c.y - e.y, c.z - e.z ),
            new Vector3( c.x - e.x, c.y + e.y, c.z + e.z ),
            new Vector3( c.x - e.x, c.y + e.y, c.z - e.z ),
            new Vector3( c.x - e.x, c.y - e.y, c.z + e.z ),
            new Vector3( c.x - e.x, c.y - e.y, c.z - e.z ),
        };

        IEnumerable<Vector3> screenCorners = worldCorners.Select(corner => Camera.main.WorldToScreenPoint(corner));
        float maxX = screenCorners.Max(corner => corner.x);
        float minX = screenCorners.Min(corner => corner.x);
        float maxY = screenCorners.Max(corner => corner.y);
        float minY = screenCorners.Min(corner => corner.y);

        Vector3 topRight = new Vector3(maxX, maxY, 0);
        Vector3 topLeft = new Vector3(minX, maxY, 0);
        Vector3 bottomRight = new Vector3(maxX, minY, 0);
        Vector3 bottomLeft = new Vector3(minX, minY, 0);

        left.position = topLeft;
        top.position = topRight;
        right.position = bottomRight;
        bottom.position = bottomLeft;
    }
emsha
  • 73
  • 1
  • 6
1

If above code is not working, below is code extracted from

http://quill18.com/unity_tutorials/unit_selection/

Function Input: any rendered visible on screen.

Output: 2D rect bounds that can be used in canvas

static Rect RendererBoundsInScreenSpace(Renderer r) {
        // This is the space occupied by the object's visuals
        // in WORLD space.
        Bounds bigBounds = r.bounds;

        if(screenSpaceCorners == null)
            screenSpaceCorners = new Vector3[8];

        Camera theCamera = Camera.main;

        // For each of the 8 corners of our renderer's world space bounding box,
        // convert those corners into screen space.
        screenSpaceCorners[0] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x + bigBounds.extents.x, bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z ) );
        screenSpaceCorners[1] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x + bigBounds.extents.x, bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z ) );
        screenSpaceCorners[2] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x + bigBounds.extents.x, bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z ) );
        screenSpaceCorners[3] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x + bigBounds.extents.x, bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z ) );
        screenSpaceCorners[4] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x - bigBounds.extents.x, bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z ) );
        screenSpaceCorners[5] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x - bigBounds.extents.x, bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z ) );
        screenSpaceCorners[6] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x - bigBounds.extents.x, bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z ) );
        screenSpaceCorners[7] = theCamera.WorldToScreenPoint( new Vector3( bigBounds.center.x - bigBounds.extents.x, bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z ) );

        // Now find the min/max X & Y of these screen space corners.
        float min_x = screenSpaceCorners[0].x;
        float min_y = screenSpaceCorners[0].y;
        float max_x = screenSpaceCorners[0].x;
        float max_y = screenSpaceCorners[0].y;

        for (int i = 1; i < 8; i++) {
            if(screenSpaceCorners[i].x < min_x) {
                min_x = screenSpaceCorners[i].x;
            }
            if(screenSpaceCorners[i].y < min_y) {
                min_y = screenSpaceCorners[i].y;
            }
            if(screenSpaceCorners[i].x > max_x) {
                max_x = screenSpaceCorners[i].x;
            }
            if(screenSpaceCorners[i].y > max_y) {
                max_y = screenSpaceCorners[i].y;
            }
        }

        return Rect.MinMaxRect( min_x, min_y, max_x, max_y );

    }
Nirav Madhani
  • 101
  • 1
  • 4
0

Sould work for both GUI Rect() and Canvas RectTransform

static Vector3[] screenSpaceCorners;

    public static Rect GetBoundsInScreenSpace(Collider collider, Camera camera)
    {
        // This is the space occupied by the object's visuals
        // in WORLD space.
        Bounds bigBounds = collider.bounds;

        if (screenSpaceCorners == null)
            screenSpaceCorners = new Vector3[8];

        // For each of the 8 corners of our renderer's world space bounding box,
        // convert those corners into screen space.
        screenSpaceCorners[0] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x + bigBounds.extents.x,
            bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z));
        screenSpaceCorners[1] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x + bigBounds.extents.x,
            bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z));
        screenSpaceCorners[2] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x + bigBounds.extents.x,
            bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z));
        screenSpaceCorners[3] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x + bigBounds.extents.x,
            bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z));
        screenSpaceCorners[4] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x - bigBounds.extents.x,
            bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z));
        screenSpaceCorners[5] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x - bigBounds.extents.x,
            bigBounds.center.y + bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z));
        screenSpaceCorners[6] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x - bigBounds.extents.x,
            bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z + bigBounds.extents.z));
        screenSpaceCorners[7] = camera.WorldToScreenPoint(new Vector3(bigBounds.center.x - bigBounds.extents.x,
            bigBounds.center.y - bigBounds.extents.y, bigBounds.center.z - bigBounds.extents.z));

        // Now find the min/max X & Y of these screen space corners.
        float min_x = screenSpaceCorners[0].x;
        float min_y = screenSpaceCorners[0].y;
        float max_x = screenSpaceCorners[0].x;
        float max_y = screenSpaceCorners[0].y;

        for (int i = 1; i < 8; i++)
        {
            if (screenSpaceCorners[i].x < min_x)
            {
                min_x = screenSpaceCorners[i].x;
            }

            if (screenSpaceCorners[i].y < min_y)
            {
                min_y = screenSpaceCorners[i].y;
            }

            if (screenSpaceCorners[i].x > max_x)
            {
                max_x = screenSpaceCorners[i].x;
            }

            if (screenSpaceCorners[i].y > max_y)
            {
                max_y = screenSpaceCorners[i].y;
            }
        }

        var rect = Rect.MinMaxRect(min_x, min_y, max_x, max_y);

        //=======For on-screen Rect() or texture ==============
        // rect = rect.ToGUISpace();   //Changing Y axis from "Screen Space" to "GUI Space", 
        // rect.position = rect.position - new Vector2(0, rect.size.y);
        //=====================================================

        //----For Canvas RectTransform------
        rect.position = rect.position + (rect.size / 2f);
        return rect;
    }

  /**
    Rect rect = SomeRect();
    rect = rect.ToGUISpace();
    https://hackingwithunity.com/coordinate-system-in-unity/
    */
    public static Rect ToGUISpace(this Rect rect)
    {
        //Changing from "Screen Space" to "GUI Space" coordinate
        rect.position = new Vector2(rect.position.x, Screen.height - rect.position.y);
        return rect;
    }
krishx007
  • 21
  • 4