- Home /
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!
Also, I know that I can get the $$anonymous$$esh out of the Renderer and call $$anonymous$$esh.bounds to get local coordinates, but my downloaded assets have different kinds of renderers, so finding the mesh becomes a horrible set of if-statements checking for different types of renderers... and some renderers don't have a $$anonymous$$esh at all! (e.g. ParticleRenderer). So I really need to convert the renderer's bounds directly.
There are only two types of renderers that have a mesh. A particleRenderer doesn't have a size since it's not a real object.
The two renderers are $$anonymous$$eshRenderer and Skinned$$anonymous$$eshRenderer. The $$anonymous$$eshRenderer actually doesn't have a mesh. The $$anonymous$$eshFilter holds it. All other types usually belong to objects that don't have / need a collider.
So just use:
// C#
public static $$anonymous$$esh Get$$anonymous$$esh(Component aComp)
{
var $$anonymous$$F = aComp.GetComponent<$$anonymous$$eshFilter>();
if ($$anonymous$$F != null)
return $$anonymous$$F.shared$$anonymous$$esh;
var S$$anonymous$$R = aComp.GetComponent<Skinned$$anonymous$$eshRenderer>();
if (S$$anonymous$$R != null)
return S$$anonymous$$R.shared$$anonymous$$esh;
return null;
}
// UnityScript
public static function Get$$anonymous$$esh(aComp : Component) : $$anonymous$$esh
{
var $$anonymous$$F = aComp.GetComponent($$anonymous$$eshFilter);
if ($$anonymous$$F != null)
return $$anonymous$$F.shared$$anonymous$$esh;
var S$$anonymous$$R = aComp.GetComponent(Skinned$$anonymous$$eshRenderer);
if (S$$anonymous$$R != null)
return S$$anonymous$$R.shared$$anonymous$$esh;
return null;
}
Thanks! But ParticleRenderer and ParticleSystemRenderer actually do have meaningful bounds -- their bounds is a box that encloses all the particles being rendered at that moment. Which is what I need to be able to fit the BoxCollider around.
Answer by Bunny83 · Dec 10, 2012 at 12:04 AM
The answer is way simpler ;)
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 ;)
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.
Thanks! You're right that AddComponent sets the collider up correctly automatically, I hadn't even noticed. However I also need to be able to update the bounds periodically when the renderer changes dramatically -- when these creatures die, they play a death animation that has a very different bounding box than their usual standing animation. So I need to be able to update the BoxCollider's bounds when the renderer's bounds change dramatically.
I can't use mesh.bounds, either. Well, I can use it for most of the assets, but some monsters in the game are composed entirely of particles -- they use ParticleRenderer, which doesn't have a mesh.
It's frustrating since Unity is obviously doing exactly what I need when it adds the component! Hmm, I guess I could remove the BoxCollider and re-add it periodically to fix up the bounds, but that seems insanely expensive...
Answer by benblo · Dec 17, 2015 at 04:55 PM
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 :) !
TIL:
Bounds.Encapsulate is super slow
Vector3 arithmetic operators (+ - * /) are slow compared to unrolling the operation on each axis...
Surely this is the reverse of what the question is asking? Doesn't this method transfrom from local bounds to world bounds? The OP is looking for the local bounds from world bounds. So am I.
Yes, he does the opposite what was asked. Actually there is no real meaningful way to convert a world bounds into local space. The problem is that a bounding box is always an enclosing box that is aligned to a certain space. Calculating the world bounds based on the local bounding box will result in a larger box when the object is rotated since the rotated local box has to fit into a world aligned box.
Doing the same thing again in reverse would mean you also get a larger bounding box in local space that would enclose the rotated world space box in local space.
Here's how it would look like:
Enlarging a bounding box always works as it still encloses the actual content. Shrinking a bounding box isn't possible without looking at the actual content that should be enclosed. If you're looking for a way to calculate the local bounding box manually, see my post over here. The code calculates the world space AABB based on the actual mesh vertices. Of course it would need to be recalculated each time the object is rotated. This might only be useful for static geometry that is rotated / positioned in the editor and will stay in it's final position.
This is only needed when the mesh is "badly" aligned in local space (like my example rectangle). If the mesh is perfectly aligned in local space (like Unity's cube for example) the local bounds would fit perfectly.
I am currently looking into a similar problem because I reimplement for an own engine a larger number of Unity3D classes into 64bit double precision pendants. I currently work on the Bounds and BoxCollider class.
In the Unity docs it says Bounds
is used by Collider.bounds
, $$anonymous$$esh.bounds
and Renderer.bounds
". Looking further Collider.bounds
and Renderer.bounds
are in worldspace while $$anonymous$$esh.bounds
is in localspace. So I thing internally the Bounds
class generally works in localspace, and also all bounds-setters generally expect localspace variables. But when reading bounds via getter, I guess in Collider and Renderer bounds is transformed to worldspace. So I am thinking about the right way to do transform the bounds from LocalSpace to WorldSpace.
I've seen another approach in Unity Answers however I think it was wrong because the edges were also rotated which I think is wrong since Bounds should always be AABB and not rotated. Your solution benblo looks correct to me. Thank you very much for posting!!
Are you sure you actually read the question? The question that was asked was the opposite of what he answered.
I actually explained that in my comment above (btw: i just fixed my image links, damn dropbox).
Yes, he does the correct local to world transformation. His result is the sameas if you would transform all eight corners of the local space AABB in worldspace and take the $$anonymous$$ / max of all components. The main difference is that his approach only works with axis aligned bounding boxes (so it's fine for Bounds) while the general $$anonymous$$-max algorithm works with any point collection.
Though again, the question asked for a world to local space conversion. World to local doesn't make much sense when you already have the local space bounds since when you use the same approach again the resulting AABB would be much larger. The conversion of an AABB between spaces will always enlarge the bounding box because you can never shrink it without knowing the actual shape of the object.
Are you sure you actually read the question?
Sure I did, pretty obvious that he didnt answer the OPs question, but still his comment was useful for those (and me) who face the opposite requirement to transform bounds from local to worldspace, e.g. when you reengineer Unity3D classes such as Transform etc. like I described.
benblo, one question to your bounds localspace-to-worldspace implementation.
I've tried your code in my custom re-implementation of the Unity-base-classes in order to enhance them to double precision (e.g. Transformd, Quaterniond, $$anonymous$$atrix4x4d, Boundsd, etc.etc.).
Your code seems to work (and is fast), however there is one thing I am uncertain which didnt initially work for me (the bounding box was too large), however my fault could be my reimplementation of Transform.TransformVector(Vector3d vector).
var axisX = _transform.TransformVector(extents.x, 0, 0);
var axisY = _transform.TransformVector(0, extents.y, 0);
var axisZ = _transform.TransformVector(0, 0, extents.z);
I am not sure if this should be TransformDirection
ins$$anonymous$$d of TransformVector
. TransformDirection
changes rotation only while TransformVector changes rotation and scale.
Say for example you have a Collider attached to a GameObject's Transform with LocalScale(2,2,2). Bounds' extents would then be (1,1,1) and so size is (2,2,2). If the GameObject is a root GameObject then its worldspace scale is localscale, so its also (2,2,2). With TransformVector being called, Bounds would be scaled up during the transformation, although it is already at the right size. Changing TransformVector to TransformDirection (with a custom Transform implementation) worked for me since the bounds size was already right.
I am not sure what is right here for a Bounds transform since indeed I also think Bounds would have to be scaled up for child gameobjects. $$anonymous$$y mistake of thinking might be what I guess happens behind the curtain at TransformVector, which i guess should be something like
public Vector3d TransformVector(Vector3d vector)
{
// Option 1: Using the transforms rotation and scale variables
Vector3d result;
result = this.rotation * vector; // $$anonymous$$ultiply by rotation
result = Vector3d.Scale(result, this.lossyScale); // Scale by worldspace scale
return result;
// Option 2: Using the TRS matrix
//$$anonymous$$atrix4x4d localToWorld$$anonymous$$atrix = $$anonymous$$atrix4x4d.TRS(Vector3d.zero, this.rotation, this.lossyScale);
//return localToWorld$$anonymous$$atrix.$$anonymous$$ultiplyPoint3x4(vector);
}
I think you're mistaken, extents do have to be scaled. Your example is confusing because Bounds.size == 2 * Bounds.extents, always, so in your example with local scale 2, you then mention a size of 2... what size are your talking about? Also, it doesn't matter if your object has a parent of not, transform.localToWorld$$anonymous$$atrix or .TransformPoint/Vector/Direction() always uses the "complete" scale (don't be confused by the local scale you see in the inspector, it's actually one of its shortco$$anonymous$$gs I$$anonymous$$O that it doesn't show the complete world TRS at least at read-only). Lastly, regarding your double API... what you want is actually $$anonymous$$atrix4x4.$$anonymous$$ultiplyVector() :)
After that post I've continued looking at my code and checking in more detail how Unity is behaving. Looks like I've indeed mistaken and confused by the inspector who shows a different collider bounds size value than what you get when you read the collider's bounds.size variable by code.
In my example where I mentioned a root gameobject (root to keep it simple, I am aware that the transforms have to take the complete scaling-chain into account) with the transform's localscale of (2,2,2) and a collider attached, the Unity inspector says bounds size is (1,1,1) but when you read bounds.size by code its (2,2,2). Thats where I got confused. Looks like the inspector accesses the bounds-classes' hidden mutable localscale size-variable while the size-getter-method transforms this variable to worldscale. Too bad they named both simply "size".
Taking this into accout I changed how it set bounds.size (or extents, which is in the end the same since as you mentioned size = 2* extents vice versa) value by code and then changed the transformation of bounds back to TransformVector. And there you go it works as expected.
https://www.youtube.com/watch?v=TZwmv$$anonymous$$D-Xng
Thanks again for your response. And also for hinting to $$anonymous$$atrix4x4.$$anonymous$$ultiplyVector, I am going to test to replace that part with my re-implementation of $$anonymous$$atrix4x4.$$anonymous$$ultiplyVector, good chance to see if it works too. :-)
Answer by Michael Schenck · Dec 09, 2012 at 09:47 PM
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.
I can see the box that Renderer.bounds returns, and it's a really nice AABB, unlike $$anonymous$$e which is at random right angles. :) So yeah, it seems like maybe what I need is to perform an additional rotation operation on the size vector when I get it back... but I don't know how to do that.
You're right, this is TransformBounds. If you want InverseTransformBounds, just replace all "_transform.TransformXxx()" with "_transform.InverseTransformXxx()"... untested though.
Answer by Venryx · Jan 09, 2015 at 09:33 AM
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);
Is your ".Times" a special method you've written? I'm currently Googling the crap out of it, but it seems like my queries are pointing to it may be a custom extension method you've written to do something on Vectors. If you see this, care to explain what it does? Thanks
Sorry! Yes, it is a custom extension method; didn't know I left its call in.
All it does is multiply the x of the "this"/first-vector by the x of the argument/second-vector, and likewise for the y and z.
So something like:
public static Vector3 Times(this Vector3 self, Vector3 other) {
return new Vector3(self.x * other.x, self.y * other.y, self.z * other.z);
}
Sorry for the confusion!
Answer by doeke · Oct 29, 2019 at 04:00 PM
var bounds = new Bounds(transform.position, Vector3.zero);
foreach (var r in GetComponentsInChildren<Collider>()) 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));
won't work when you have rotation set on your transform