Can't convert Bounds from world coordinates to local coordinates

I’m downloading assets at runtime and fixing them up dynamically. One of the things I need to do is add a BoxCollider to an object that has a renderer on it, and make the box collider perfectly fit overtop the rendered object. Renderer has a variable bounds which returns its bounds in world coordinates. BoxCollider has two members, center and size, which are in local coordinates. So what I need to do is convert the Renderer’s world-coordinates Bounds into a local-coordinate center and size. This is the code I have:

BoxCollider box = myRenderer.gameObject.AddComponent<BoxCollider>();
box.isTrigger = true;
box.center = myRenderer.transform.InverseTransformPoint(myRenderer.bounds.center);
box.size = myRenderer.transform.InverseTransformDirection(myRenderer.bounds.size);

This code works for SOME objects, but not others. It depends on what other transforms are parented above the renderer. In other words, it’s not transforming it right all the time. The center point is usually (maybe always?) correct, but the size variable is often wrong. The box is the right size, but it isn’t aligned correctly over the renderer. It’ll be perpendicular in one or more dimensions, e.g. on an object that’s that’s long and squat, the collider will be tall and thin.

I guess the InverseTransformDirection() function can’t correctly transform a vector that contains a size? What can?

Can someone explain what’s going wrong, and what I can do about it?

Thanks!

The answer is way simpler :wink:


renderer.bounds returns the axis-aligned bounding box in world space coordinates.
mesh.bounds returns the axis-aligned bounding box in local space coordinates.

(Note. These days for the new “sprite” type, there is no mesh. For a sprite,

for local bounds GetComponent<SpriteRenderer>().sprite.bounds

for world bounds, GetComponent<Renderer>().bounds )


But you actually don’t need those, because when you use AddComponent to add a BoxCollider to an object that has a renderer with a mesh, it will automatically center and resize the collider to fit the mesh. So just Add the Collider :wink:

ps. Keep in mind that using renderer.bounds is quite useless to set local properties of an object since the size of the collider is also affected by the objects scale in addition to position and rotation.

I use this:

		public static Bounds TransformBounds( this Transform _transform, Bounds _localBounds )
		{
			var center = _transform.TransformPoint(_localBounds.center);

			// transform the local extents' axes
			var extents = _localBounds.extents;
			var axisX = _transform.TransformVector(extents.x, 0, 0);
			var axisY = _transform.TransformVector(0, extents.y, 0);
			var axisZ = _transform.TransformVector(0, 0, extents.z);

			// sum their absolute value to get the world extents
			extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x);
			extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y);
			extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z);

			return new Bounds { center = center, extents = extents };
		}

After some tests, it’s about 10x faster than the proposed “iterate on all corners” option by @Venryx. And hopefully it works :slight_smile: !

TIL:

  • Bounds.Encapsulate is super slow
  • Vector3 arithmetic operators (+ - * /) are slow compared to unrolling the operation on each axis…

I haven’t thoroughly tested it yet, but this is what I’m using:

// transform
public static Bounds TransformBounds(this Transform self, Bounds bounds)
{
	var center = self.TransformPoint(bounds.center);
	var points = bounds.GetCorners();

	var result = new Bounds(center, Vector3.zero);
	foreach (var point in points)
		result.Encapsulate(self.TransformPoint(point));
	return result;
}
public static Bounds InverseTransformBounds(this Transform self, Bounds bounds)
{
	var center = self.InverseTransformPoint(bounds.center);
	var points = bounds.GetCorners();

	var result = new Bounds(center, Vector3.zero);
	foreach (var point in points)
		result.Encapsulate(self.InverseTransformPoint(point));
	return result;
}

// bounds
public static List<Vector3> GetCorners(this Bounds obj, bool includePosition = true)
{
	var result = new List<Vector3>();
	for (int x = -1; x <= 1; x += 2)
		for (int y = -1; y <= 1; y += 2)
			for (int z = -1; z <= 1; z += 2)
				result.Add((includePosition ? obj.center : Vector3.zero) + (obj.size / 2).Times(new Vector3(x, y, z)));
	return result;
}

You then can use it as follows:

var localBounds = transform.InverseTransformBounds(worldBounds);

One thing to consider is that Bounds are axis aligned bounding boxes. You should make sure to set the transformation instance position to the origin and rotation to identity and scale to one and have no parent then simply copy the renderer bounds over as the box collider bounds. Although this may be more work than going for the mesh local space bounds. What could be happening is that depending on the position/rotation/parenting the instance is at some rotation when you sample the renderer bounds and this gives bounds that may be oblong or different and only for an axis aligned bounding box of the mesh at that rotation. You then try to transform that and set it as local coordinates and it won’t come out correctly. The center should work if you do the point transformation since it isn’t different based on the rotation. Transforming the size probably further distorts things since this is going to take into account rotation and since it is axis aligned all this involves is scale - size is not a point in space, but a vector from the center to the max aligned to the axis. Hope this help.

var bounds = new Bounds(transform.position, Vector3.zero);
foreach (var r in GetComponentsInChildren()) bounds.Encapsulate(r.bounds);
bounds.center = transform.InverseTransformPoint(bounds.center);
var sizeVec = transform.InverseTransformVector(bounds.size);
bounds.size.Set(Mathf.Abs(sizeVec.x), Mathf.Abs(sizeVec.y), Mathf.Abs(sizeVec.z));

Hopefully this helps somebody. Is not the specific answer to this question but could be useful. I use this method in several projects and works very fast.

This transforms the bounds itself, and then generates another bounds that holds the one that has been transformed inside. This can be used to make sure that any transformation is inside the new bbox.

/// <summary> Transform center and extents of the given bounds transformed by the transformation matrix passed. </summary>
public static void TransformBounds(ref Bounds bounds, in Matrix4x4 matrix)
{
    var xa = matrix.GetColumn(0) * bounds.min.x;
    var xb = matrix.GetColumn(0) * bounds.max.x;

    var ya = matrix.GetColumn(1) * bounds.min.y;
    var yb = matrix.GetColumn(1) * bounds.max.y;

    var za = matrix.GetColumn(2) * bounds.min.z;
    var zb = matrix.GetColumn(2) * bounds.max.z;

    var col4Pos = matrix.GetColumn(3);

    Vector3 min = new Vector3();
    min.x = Mathf.Min(xa.x, xb.x) + Mathf.Min(ya.x, yb.x) + Mathf.Min(za.x, zb.x) + col4Pos.x;
    min.y = Mathf.Min(xa.y, xb.y) + Mathf.Min(ya.y, yb.y) + Mathf.Min(za.y, zb.y) + col4Pos.y;
    min.z = Mathf.Min(xa.z, xb.z) + Mathf.Min(ya.z, yb.z) + Mathf.Min(za.z, zb.z) + col4Pos.z;

    Vector3 max = new Vector3();
    max.x = Mathf.Max(xa.x, xb.x) + Mathf.Max(ya.x, yb.x) + Mathf.Max(za.x, zb.x) + col4Pos.x;
    max.y = Mathf.Max(xa.y, xb.y) + Mathf.Max(ya.y, yb.y) + Mathf.Max(za.y, zb.y) + col4Pos.y;
    max.z = Mathf.Max(xa.z, xb.z) + Mathf.Max(ya.z, yb.z) + Mathf.Max(za.z, zb.z) + col4Pos.z;

    bounds.SetMinMax(min, max);
}

The next version uses Mathematics package (math instead of Mathf, float4 instead of Vector3…) + Burst package installed and enabled its compilation, to improve efficiency in mathematics operations using SIMD.

If you really want to improve efficiency in maths operations:
https://docs.unity3d.com/Packages/com.unity.mathematics@1.2/manual/index.html

/// <summary> Transform center and extents of the given bounds transformed by the transformation matrix passed. </summary>
public static void TransformBounds(ref float4 min, ref float4 max, in float4x4 matrix)
{
            var xa = matrix.c0 * min.x;
            var xb = matrix.c0 * max.x;

            var ya = matrix.c1 * min.y;
            var yb = matrix.c1 * max.y;

            var za = matrix.c2 * min.z;
            var zb = matrix.c2 * max.z;

            var col4Pos = new float4(matrix.c3.xyz, 0);
            min = math.min(xa, xb) + math.min(ya, yb) + math.min(za, zb) + col4Pos;
            max = math.max(xa, xb) + math.max(ya, yb) + math.max(za, zb) + col4Pos;
 }

So if you want to convert the bounds from one space to another one (say, world space), use your local to world transform matrix, and it will work like a charm. :wink: