How do you find the size of a local bounding box?

I have an object with an arbitrary rotation. I need to know its total size in all three dimensions

collider.bounds.size is not helpful, because it returns the size of a world-axis aligned bounding box, which can significantly change depending on how the object is rotated. I’ve tried running it through transformPoint and inversetransformPoint, those didn’t help

I need a way to figure out the actual size of the object. Specifically, i need the size of the collider’s unrotated bounding box.
The object may or may not have a mesh on it, and the mesh bounds are functionally irrelevant anyway

Apparently this is a complex problem. but i’m sure it’s come up before, and there must be a robust solution by now.

The only thing i can think of is temporarily setting its rotation to zero, measuring it, and then setting it back. that seems hugely messy, and is probably asking for other problems.

2 Likes

I think you want to get the mesh out of the mesh filter and query its bounds.

See this reference:

1 Like

Nope, this is not what i want.

While i’m testing on a simple stretched cube just now, i’m writing code to be used on a ragdoll. Which will consist of one main character mesh, and many smaller colliders/rigidbodies/joints attached to the bones of that mesh. This code is for calculating the size of the collider of any of those parts. Querying the mesh will not give anything useful here.

That said, is it possible to query The mesh of the collider? I intend to use a mixture of box, capsule, sphere and mesh colliders. The first three are still meshes to some degree aren’t they?

I also have a related question up on unity answers, knowing the answer to that would help me here: Reading and Setting an object's global scale, with transform functions? - Questions & Answers - Unity Discussions

It seems like one of the six Transform.Transform*** functions might be helpful, but i’m not sure. Perhaps someone could providesome help on how to do it with matrices?

The picture is starting to clear up for me. I think in practice, you will never have an object of type Collider because this is the base class of all other colliders. It’s not clear from the documentation if it is an abstract class, so I suppose it is possible you could end up with one at runtime. Anyways, assume for the moment that a Collider is always one of the following:

It looks like each of these has its own way of detecting the actual size of the collider. For basic shapes, we can get a precise size and radius. For the MeshCollider, the best we can do is calculate the bounding box.

Since we want a common property across all of these types, the best we can do is the most generic shape which can contain every type of collider; a bounding box. Let’s come up with a common way to access this bounding box.

The simplest way to do this is runtime type analysis and an ugly block of if statements. I’ll start with that and refactor later.

class ColliderMeasurer
{
  public Vector3 GetBoundingBox(Collider collider)
  {
    if (collider is BoxCollider)
      return ((BoxCollider)collider).size;
    else if (collider is SphereCollider)
    {
      var radius = ((SphereCollider)collider).radius;
      return new Vector3(radius * 2, radius * 2, radius * 2);
    }
    else if (collider is CapsuleCollider)
    {
      var radius = ((CapsuleCollider)collider).radius;
      var height = ((CapsuleCollider)collider).height;
      var direction = ((CapsuleCollider)collider).direction;

      var directionArray = new Vector3[] { Vector3.right, Vector3.up, Vector3.forward };
      var result = new Vector3();
      for (int i = 0; i < 3; i ++)
      {
        if (i == direction)
          result += directionArray[i] * height;
        else
          result += directionArray[i] * radius * 2;
      }
      return result;
    }
    else if (collider is MeshCollider)
    {
      return ((MeshCollider)collider ).sharedMesh.bounds.size;
    }
  }
}

So that’s ugly… but I think it will work. Now let’s put our refactoring hat on and se if we can tidy it up. The next bit is pretty long so read on if you’re feeling brave…

Whenever I see a block of else if, or a case statement, I consider replacing it with a strategy pattern. In this case, I think it’s a good fit. Let’s create a base strategy and replace each if statement with a concrete implementation of that strategy.

abstract class ColliderMeasureStrategy
{
  public abstract Vector3 GetBoundingBox(Collider collider);
}

class BoxColliderMeasureStrategy : ColliderMeasureStrategy
{
  public override Vector3 GetBoundingBox(Collider collider)
  {
    return GetBoundingBox(collider as BoxCollider);
  }

  public Vector3 GetBoundingBox(BoxCollider collider)
  {
    return collider.size;
  }
}

class SphereColliderMeasureStrategy : ColliderMeasureStrategy
{
  public override Vector3 GetBoundingBox(Collider collider)
  {
    return GetBoundingBox(collider as SphereCollider);
  }

  public Vector3 GetBoundingBox(SphereCollider collider)
  {
    return new Vector3(collider.raidus * 2, collider.raidus * 2, collider.raidus * 2);
  }
}

class CapsuleColliderMeasureStrategy : ColliderMeasureStrategy
{
  public override Vector3 GetBoundingBox(Collider collider)
  {
    return GetBoundingBox(collider as CapsuleCollider);
  }

  public Vector3 GetBoundingBox(CapsuleCollider collider)
  {
    var directionArray = new Vector3[] { Vector3.right, Vector3.up, Vector3.forward };
    var result = new Vector3();
    for (int i = 0; i < 3; i ++)
    {
      if (i == direction)
        result += directionArray[i] * height;
      else
        result += directionArray[i] * radius * 2;
    }
    return result;
  }
}

class MeshColliderMeasureStrategy : ColliderMeasureStrategy
{
  public override Vector3 GetBoundingBox(Collider collider)
  {
    return GetBoundingBox(collider as MeshCollider);
  }

  public Vector3 GetBoundingBox(MeshCollidercollider)
  {
    return collider.sharedMesh.bounds.size;
  }
}

This is looking better to me but I want to make a few more changes…

  • I will create a namespace specifically for this strategy, so to clear up my main namespace
  • I will change the class names a bit - I don’t typically like putting the name of design patterns into my class names
  • I will refactor the CapsuleColliderStrategy class to make it easier to understand
namespace ColliderMeasureStrategy
{
  abstract class ColliderMeasurer
  {
    public abstract Vector3 GetBoundingBox(Collider collider);
  }

  class BoxColliderMeasurer : ColliderMeasurer
  {
    public override Vector3 GetBoundingBox(Collider collider)
    {
      return GetBoundingBox(collider as BoxCollider);
    }

    public Vector3 GetBoundingBox(BoxCollider collider)
    {
      return collider.size;
    }
  }

  class SphereColliderMeasurer : ColliderMeasurer
  {
    public override Vector3 GetBoundingBox(Collider collider)
    {
      return GetBoundingBox(collider as SphereCollider);
    }

    public Vector3 GetBoundingBox(SphereCollider collider)
    {
      return new Vector3(collider.raidus * 2, collider.raidus * 2, collider.raidus * 2);
    }
  }

  class CapsuleColliderMeasurer : ColliderMeasurer
  {
    static readonly Vector3[] directions = new Vector3[] { Vector3.right, Vector3.up, Vector3.forward };

    public override Vector3 GetBoundingBox(Collider collider)
    {
      return GetBoundingBox(collider as CapsuleCollider);
    }

    public Vector3 GetBoundingBox(CapsuleCollider collider)
    {
      var result = new Vector3();
      for (int i = 0; i < 3; i ++)
      {
        if (i == direction)
          result += directions[i] * height;
        else
          result += directions[i] * radius * 2;
      }
      return result;
    }
  }

  class MeshColliderMeasurer : ColliderMeasurer
  {
    public override Vector3 GetBoundingBox(Collider collider)
    {
      return GetBoundingBox(collider as MeshCollider);
    }

    public Vector3 GetBoundingBox(MeshCollidercollider)
    {
      return collider.sharedMesh.bounds.size;
    }
  }
}

Finally, we need an entry point to use our strategy… Normally I would leave it up to the owner of the Collider to determine what kind of ColliderMeasurer it needs. I would use an abstract factory to create both the Collider and ColliderMeasurer at the same time. However, in this case the Collider is created by Unity and just handed to us, so we’ll need something else.

Lets add a static convenience creation method to our base strategy which can create the correct measuring strategy based on type. There is still some type checking here but at least it is done using a registration model.

abstract class ColliderMeasurer
{
  static Dictionary<Type, ColliderMeasurer> registeredMeasurers = new Dictionary<Type, ColliderMeasurer>()
                                                      { { typeof(BoxCollider), new BoxColliderMeasurer() },
                                                        { typeof(SphereCollider), new SphereColliderMeasurer() },
                                                        { typeof(CapsuleCollider), new CapsuleColliderMeasurer() },
                                                        { typeof(MeshCollider), new MeshColliderMeasurer() } };

  public static ColliderMeasurer ForCollider(Collider collider)
  {
    if (registeredMeasurers.ContainsKey(collider.GetType())
      return registeredMeasurers[collider.GetType()];
    else
      throw new NotImplementedException(string.Format("The measurer strategy for collider type {0} is not implemented", collider.GetType().FullName));
  }

  public abstract Vector3 GetBoundingBox(Collider collider);
}

We could keep going - maybe make the constructors for each concrete strategy private so that people don’t accidentally use this pattern incorrectly but I think that’s far enough for now… Especially since I’m not even 100% sure this is going to do what you need!

2 Likes

@eisenpony
We had the same idea, ironically, i figured this out a couple of hours before you posted. Currently coding to test its suitability, but the premise of calculating a base size for each collider seems sound.

Once i have that i just need to account for scales (and not rotations) in the hierarchy above it, right?

Will any of the transformPoint/Vector/Direction functions accomplish that, or will i have to write something to recurse up the hierarchy?

re: Strategy patterns; This is new to me, i shall read and learn.

Thank you ! :slight_smile:

Oh dear, this doesn’t work

Child objects are skewed by scales of the parents if there’s a rotation difference between them. This is pretty messed up. On the right there is a single object with a boxcollider of identical shape and size

the idea does seem to work for getting the basic size of the bounding box, but scaling it up beyond that is fraught with issues. However i do notice that the collider doesn’t skew with the mesh, maybe getting meaningful data about the size of the collider is still possible, that’s what i really wanted anyway

Ok in addition i notice that, if i remove a skewed child object from its parent, the mesh snaps back to conform to the unskewed collider, and the roitation and scale of the object correct themselves to keep it in the same point in worldspace.

So whatever i need to be done is possible, and unity already does it in this circumstance. I just need to know how to replicate exactly what unity does here.

I do not need the skew values

Super helpful, thanks!!