Boxcast + rotation yields invalid results?

Hello dear community,

I’m currently building a component that should detect whether there is a surface under the player or not. The plan is currently to only use collider and raycasting.

I have now gotten the code to run so far that colliders that are located under the component are found.

9388229--1313354--Example_01.JPG 9388229--1313357--Example_02.JPG

However, when I use the code with a rotation, it seems to break. I don’t understand why this happens.

9388229--1313360--Example_03.JPG

Does anyone here have an idea?

private bool SearchForSurface()
{
   // Clearing the array so no old hits are used while iterating
  System.Array.Clear(_raycastCache, 0, _raycastCache.Length);
  if (_interactionObject.Collider is BoxCollider boxCollider)
  {
     var bounds = boxCollider.bounds;
     int hitCount = Physics.BoxCastNonAlloc(bounds.center,
                    bounds.extents,
                    Vector3.down,
                    _raycastCache,
                    transform.rotation,
                    100f,
                    hitMask,
                    QueryTriggerInteraction.Ignore);

     if (hitCount == 0)
     {
        Debug.Log("No hits");
        return false;
     }

     for (int i = 0; i < hitCount; i++)
     {
        var hit = _raycastCache[i];
        if (hit.collider == boxCollider)
        {
           continue;
        }
        Debug.Log($"HIT: {hit.collider.name}");
     }
     return true;
   }
   return false;
}

Please note that I’m only changing the rotation of the object during testing. No other values are changed.

Best regards,
Ben

Do you know what Bounds are? They’re an AABB so you’re taking that large bounds “box” as a specification for the size of the box then rotating it. The size of the box you pass should be the size of the box, not its bounds. The two are the same when non-rotated but not so when rotated.

The bounds.center should be okay although technically it’s the BoxCollider.center transformed by the Transform, but the bounds.extents is not correct, it should be BoxCollider.size * 0.5f. Note that you’ll need to scale this if you’re scaling.

Thanks for the fast clarification. Then that was a misunderstanding on my part.
For anyone interested:

var t = transform;
int hitCount = Physics.BoxCastNonAlloc(t.position,
                    boxCollider.size * 0.5f,
                    Vector3.down,
                    RaycastCache,
                    t.rotation,
                    100f,
                    hitMask,
                    QueryTriggerInteraction.Ignore);

This will yield correct results.

Note though that you’ll need to scale the size if you’ve got scaling. This is likely why for the non-rotated case, “Bounds” are used.

Thanks for the hint. So I guess I have to iterate over all parents (if there are any) and multiply the scale for the collider by the scale for the parents? Something like this:

Parent_A (Scale = (0.5,0.5,0.5)
=> Parent_B (Scale = (0.25,0.25,0.25)
=> Object (Scale = (0.5,0.5,0.5) , Collider Size (1,1,1))

So the final scale for the box collider would be:

x => 0.5 * 0.25 * 0.5 * 1 = 0.0625
y => 0.5 * 0.25 * 0.5 * 1 = 0.0625
z => 0.5 * 0.25 * 0.5 * 1 = 0.0625

If I’m correct, is there a function in Unity that will help me get the right scale or do I have to manually iterate over all the parent objects?

I also wanted to ask how the scale works for a sphere collider. I noticed that the Gizmo in the editor orients itself on the axis with the highest value. Would the following code be correct then?

private float GetRealSphereRadius(float radius)
{
      var cs = transform.localScale;
      float max = Mathf.Max(cs.x, cs.y);
      max = Mathf.Max(max, cs.z);
      return radius * max;
}

And how do you deal with parents here? Do you always have to use the highest axis or do you keep the axis that defines the scale on the object. Here’s an example:

Parent_A (Scale = (1,0.5,0.5)
=> Parent_B (Scale = (0.25,1,0.25)
=> Object (Scale = (0.5,0.5,1) , Collider Size (0.1,0.1,0.1))

  1. radius = 1 * 1 * 1 * 1
  2. radius = 0.5 * 0.25 * 1 * 0.1

Would be 1 or 2 correct here? Or am I totally wrong?

No. You can use: https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html

Yes, it’s similar to your code except that you use lossyScale (not local scale) and you ensure you use the absolute value of each of the scale components and finally ensure that its clamped to a minimum radius:

private float GetRealSphereRadius(float radius)
{
    var scale = transform.lossyScale;
    var cs = new Vector3(Mathf.Abs(scale.x), Mathf.Abs(scale.x), Mathf.Abs(scale.z));
    float max = Mathf.Max(Mathf.Max(Mathf.Max(cs.x, cs.y), cs.z);
    return Mathf.Max(radius * max, 0.00001f);
}

Note that I’m not a 3D physics dev but a 2D physics dev but I just looked at the source. :wink:

Internally it’s this C++ code:

<snip>

Vector3f scale = worldScaleLossy;
float absoluteRadius = std::max(std::max(Abs(scale.x), Abs(scale.y)), Abs(scale.z)) * m_Radius;
absoluteRadius = Abs(absoluteRadius);
absoluteRadius = std::max(absoluteRadius, 0.00001F);
return absoluteRadius;

Thanks for your help. Guess I finally understand why lossyScale exists now :smile:

Yes, it’s essentially “.scale” but the word “lossy” in its name is hinting at its inability to support “skew”. Personally, I don’t like the naming of it but it is what it is. :slight_smile: