Finding a 3D objects 2D bounds

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.

1537445--89232--$rotation-example.jpg

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

			Bounds b = renderer.bounds;
			Camera cam = Camera.main;
			Vector3[] pts = new Vector3[8];
			float margin = 0f;
			
			//The object is behind us
			if (cam.WorldToScreenPoint (b.center).z < 0) return;
			
			//All 8 vertices of the bounds
			pts[0] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z));
			pts[1] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z - b.extents.z));
			pts[2] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y - b.extents.y, b.center.z + b.extents.z));
			pts[3] = cam.WorldToScreenPoint (new Vector3 (b.center.x + b.extents.x, b.center.y - b.extents.y, b.center.z - b.extents.z));
			pts[4] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z));
			pts[5] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y + b.extents.y, b.center.z - b.extents.z));
			pts[6] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y - b.extents.y, b.center.z + b.extents.z));
			pts[7] = cam.WorldToScreenPoint (new Vector3 (b.center.x - b.extents.x, b.center.y - b.extents.y, b.center.z - b.extents.z));
			
			//Get them in GUI space
			for (int i=0;i<pts.Length;i++) pts[i].y = Screen.height-pts[i].y;
			
			//Calculate the min and max positions
			Vector3 min = pts[0];
			Vector3 max = pts[0];
			for (int i=1;i<pts.Length;i++) {
				min = Vector3.Min (min, pts[i]);
				max = Vector3.Max (max, pts[i]);
			}
			
			//Construct a rect of the min and max positions and apply some margin
			Rect r = Rect.MinMaxRect (min.x,min.y,max.x,max.y);
			r.xMin -= margin;
			r.xMax += margin;
			r.yMin -= margin;
			r.yMax += margin;
			
			//Render the box
			GUI.Box (r,"");

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!

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. :slight_smile:

EDIT: Sorry, but I am wrong. :frowning: Vector.Min and Vector.Max should work fine.

Bounds structure will help your question.

Bounds.Contains(Vector3 position)

Bounds.size.y will be 10000000.

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.

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

			float minX = pts[0].x;
			float maxX = pts[0].x;
			float minY = pts[0].y;
			float maxY = pts[0].y;
			for (int i=1;i<pts.Length;i++) {
				minX = Mathf.Min( pts[ i ].x, minX );
				maxX = Mathf.Max( pts[ i ].x, maxX );
				minY = Mathf.Min( pts[ i ].y, minY );
				maxY = Mathf.Max( pts[ i ].y, maxY );
			}
			Rect r = new Rect( minX, minY, maxX - minX, maxY - minY );

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

1537499--89234--$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…

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.

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

1537546--89240--$point-in-3d.jpg

using UnityEngine;
using UnityEditor;
using System.Collections;

public class unit : MonoBehaviour {
	public bool selected = false;
	public Texture2D outline = null;
	private GameObject cube = null;

	// Use this for initialization
	void Start () {
		cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
		cube.transform.localScale = new Vector3 (0.3f, 0.3f, 0.3f);
	}

	void OnGUI(){
		if (true) {
			Bounds b = renderer.bounds;
			cube.transform.position = new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z);
			Vector3 pts = HandleUtility.WorldToGUIPoint (new Vector3 (b.center.x + b.extents.x, b.center.y + b.extents.y, b.center.z + b.extents.z));

			GUI.Box (new Rect(pts.x-5, pts.y -5, 10,10 ), "" );
		}
	}
}

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 :slight_smile:

This code calculates the rectangle out from the objects vertices and it works. You should never use this code on a high poly object.

using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

	public float margin = 10f;
	public GameObject go;
	private Vector3[] v;

	Camera cam;

	void Start()
	{
		cam = Camera.main;
		v = go.GetComponent<MeshFilter>().mesh.vertices;
	}


	void OnGUI()
	{
		//The object is behind us
		if (cam.WorldToScreenPoint (go.renderer.bounds.center).z < 0) return;

		v = go.GetComponent<MeshFilter>().mesh.vertices;

		for(int i = 0; i < v.Length; i++)
		{
			//World space
			v[i] = go.transform.TransformPoint(v[i]);
			//GUI space
			v[i] = cam.WorldToScreenPoint(v[i]);
			v[i].y = Screen.height - v[i].y;
		}

		Vector3 min = v[0];
		Vector3 max = v[0];
		
		for (int i = 1; i < v.Length; i++) {
			
			min = Vector3.Min (min, v[i]);
			max = Vector3.Max (max, v[i]);
		}
		
		//Construct a rect of the min and max positions and apply some margin
		Rect r = Rect.MinMaxRect (min.x,min.y,max.x,max.y);
		
		r.xMin -= margin;
		r.xMax += margin;
		r.yMin -= margin;
		r.yMax += margin;		
		
		//Render the box
		GUI.Box (r,"");
	}
}