Workaround for Physics.OverlapBox

Unity's Physics.OverlapBox intermittently crashes on Android devices. The crash is a seg fault from inside libunity.so and looks like it's from the PhysX integration.

I found this forum post discussing the crash and saw that another user, Bunzaga, posted a useful Box-Box collision test algorithm that could be used in a workaround. I came up with a performant workaround by using Physics.OverlapSphere to find nearby colliders, and then using Bunzaga's box-box collision test to filter the nearby colliders. The box-box collision test is based on this useful gamasutra article.

A sphere fully surrounding a cube. The sphere's radius is half the length of the vector (boxlength, boxwidth, boxdepth).

A sphere fully surrounding a cube. The sphere's radius is half the length of the vector (boxlength, boxwidth, boxdepth).

The algorithm only works correctly for BoxColliders. If you need accurate SphereCollider results, you can extend it by adding a section which checks for SphereCollider's and implementing the Box-Sphere collision test found on this gamasutra article.


The code is posted in full below, and exposes a CollisionCheck.OverlapBox which will not crash on Android.

CollisionCheck.cs

using UnityEngine;
// adapted by 00jknight from Bunzaga's post on https://forum.unity3d.com/threads/solved-unfixable-bug-random-crashes-within-overlapbox-on-android.410868/
public static class CollisionCheck
{
    private static Vector3 _AB = new Vector3(); // Direction A to B
    private static float[] _R = new float[9]; // 3x3 Rotation
    private static float[] _AbsR = new float[9]; // 3x3 Rotation
    private static Vector3 _AX = new Vector3(); // A Axis
    private static Vector3 _BX = new Vector3(); // B Axis
 
    private static Vector3 _v1 = new Vector3();
    private static Vector3 _v2 = new Vector3();
    private static Vector3 _v3 = new Vector3();
 
    private static float[] _aRot = new float[9];
    private static float[] _bRot = new float[9];
 
    private static float ar = 0.0f, br = 0.0f;
 
    private static float[] _identityMatrix = new float[9] { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f };

    private static Collider[] sphere_cast_collider_storage = new Collider[32];
	
	public static int OverlapBox(Vector3 center, Vector3 halfExtents, Quaternion rotation, Collider[] colliderStorage, LayerMask killLayer) {
		var radius = 0.5f * new Vector3(halfExtents.x*2.0f, halfExtents.y*2.0f, halfExtents.z*2.0f).magnitude;
		int numOverlaps = Physics.OverlapSphereNonAlloc(center, radius, sphere_cast_collider_storage, killLayer.value);
		int numBoxOverlaps = 0;
		for (int i = 0; i < numOverlaps; i++) {
			var nearbyCollider = sphere_cast_collider_storage[i];
			//Debug.Log("Testing against " + nearbyCollider.gameObject.name);
			var nearbyBoxCollider = nearbyCollider.GetComponent();
			var nearbyColliderHalfExtents = nearbyBoxCollider != null ? HalfExtentsFromBoxCollider(nearbyBoxCollider) : nearbyCollider.transform.localScale * 0.5f;
			var nearbyColliderPosition = nearbyBoxCollider != null ? PositionFromCollider(nearbyBoxCollider) : nearbyCollider.transform.position;
			//Debug.Log("Position: " + nearbyCollider.transform.position);
			//Debug.Log("Rotation: " + nearbyCollider.transform.rotation);
			//Debug.Log("Half Extents: " + nearbyColliderHalfExtents);
			//Debug.Log("Has box: " + (nearbyBoxCollider != null).ToString());
			if (CollisionCheck.Box_Box(center, halfExtents, rotation, nearbyColliderPosition, nearbyColliderHalfExtents, nearbyCollider.transform.rotation)) {
				colliderStorage[numBoxOverlaps++] = sphere_cast_collider_storage[i];
			}
		}
		return numBoxOverlaps;
	}
	public static Vector3 HalfExtentsFromBoxCollider(BoxCollider boxCollider1) {
		return new Vector3(boxCollider1.size.x * boxCollider1.transform.localScale.x * 0.5f, boxCollider1.size.y * boxCollider1.transform.localScale.y * 0.5f, boxCollider1.size.z * boxCollider1.transform.localScale.z * 0.5f);
	}
	public static Vector3 PositionFromCollider(BoxCollider collider) {
		return collider.transform.position + collider.center;
	}
    
    
    // Adapted from: http://www.gamasutra.com/view/feature/131790/simple_intersection_tests_for_games.php?print=1
    public static bool Box_Box (Vector3 aPos, Vector3 aSize, Quaternion aQuat, Vector3 bPos, Vector3 bSize, Quaternion bQuat)
    {
        ar = 0.0f;
        br = 0.0f;
 
        QuaternionToFloatArray(aQuat, _aRot);
        QuaternionToFloatArray(bQuat, _bRot);
 
 
        for (int i = 0, i1 = 0; i < 3; i++, i1 += 3)
        {
            _AX.Set(_aRot[i1], _aRot[i1 + 1], _aRot[i1 + 2]);
            for (int j = 0, j1 = 0; j < 3; j++, j1 += 3)
            {
                _BX.Set(_bRot[j1], _bRot[j1 + 1], _bRot[j1 + 2]);
                _R[i1 + j] = Vector3.Dot(_AX, _BX);
            }
        }
 
        _AB = bPos - aPos;
 
        _v1.Set(_aRot[0], _aRot[1], _aRot[2]);
        _v2.Set(_aRot[3], _aRot[4], _aRot[5]);
        _v3.Set(_aRot[6], _aRot[7], _aRot[8]);
 
        _AB.Set(Vector3.Dot(_AB, _v1), Vector3.Dot(_AB, _v2), Vector3.Dot(_AB, _v3));
 
        for (int i = 0; i < 9; i++)
        {
            _AbsR[i] = Mathf.Abs(_R[i]) + 0.001f;
        }
        // Test axes L = A0, L = A1, L = A2
        for (int i = 0, i1 = 0; i < 3; i++, i1 += 3)
        {
            ar = aSize[i];
            br = (bSize[0] * _AbsR[i1]) + (bSize[1] * _AbsR[i1 + 1]) + (bSize[2] * _AbsR[i1 + 2]);
            if (Mathf.Abs(_AB[i]) > (ar + br)) { return false; }
        }
        // Test axes L = B0, L = B1, L = B2
        for (int i = 0; i < 3; i++)
        {
            ar = (aSize[0] * _AbsR[i]) + (aSize[1] * _AbsR[i + 3]) + (aSize[2] * _AbsR[i + 6]);
            br = bSize[i];
            if (Mathf.Abs((_AB[0] * _R[i]) + (_AB[1] * _R[i + 3]) + (_AB[2] * _R[i + 6])) > (ar + br)) { return false; }
        }
        // Test axis L = A0 x B0
        ar = (aSize[1] * _AbsR[6]) + (aSize[2] * _AbsR[3]);
        br = (bSize[1] * _AbsR[2]) + (bSize[2] * _AbsR[1]);
        if (Mathf.Abs(_AB[2] * _R[3] - _AB[1] * _R[6]) > (ar + br)) { return false; }
        // Test axis L = A0 x B1
        ar = (aSize[1] * _AbsR[7]) + (aSize[2] * _AbsR[4]);
        br = (bSize[0] * _AbsR[2]) + (bSize[2] * _AbsR[0]);
        if (Mathf.Abs((_AB[2] * _R[4]) - (_AB[1] * _R[7])) > (ar + br)) { return false; }
        // Test axis L = A0 x B2
        ar = aSize[1] * _AbsR[8] + aSize[2] * _AbsR[5];
        br = bSize[0] * _AbsR[1] + bSize[1] * _AbsR[0];
        if (Mathf.Abs((_AB[2] * _R[5]) - (_AB[1] * _R[8])) > (ar + br)) { return false; }
        // Test axis L = A1 x B0
        ar = (aSize[0] * _AbsR[6]) + (aSize[2] * _AbsR[0]);
        br = (bSize[1] * _AbsR[5]) + (bSize[2] * _AbsR[4]);
        if (Mathf.Abs((_AB[0] * _R[6]) - (_AB[2] * _R[0])) > (ar + br)) { return false; }
        // Test axis L = A1 x B1
        ar = (aSize[0] * _AbsR[7]) + (aSize[2] * _AbsR[1]);
        br = (bSize[0] * _AbsR[5]) + (bSize[2] * _AbsR[3]);
        if (Mathf.Abs((_AB[0] * _R[7]) - (_AB[2] * _R[1])) > (ar + br)) { return false; }
        // Test axis L = A1 x B2
        ar = (aSize[0] * _AbsR[8]) + (aSize[2] * _AbsR[2]);
        br = (bSize[0] * _AbsR[4]) + (bSize[1] * _AbsR[3]);
        if (Mathf.Abs((_AB[0] * _R[8]) - (_AB[2] * _R[2])) > (ar + br)) { return false; }
        // Test axis L = A2 x B0
        ar = (aSize[0] * _AbsR[3]) + (aSize[1] * _AbsR[0]);
        br = (bSize[1] * _AbsR[8]) + (bSize[2] * _AbsR[7]);
        if (Mathf.Abs((_AB[1] * _R[0]) - (_AB[0] * _R[3])) > (ar + br)) { return false; }
        // Test axis L = A2 x B1
        ar = (aSize[0] * _AbsR[4]) + (aSize[1] * _AbsR[1]);
        br = (bSize[0] * _AbsR[8]) + (bSize[2] * _AbsR[6]);
        if (Mathf.Abs((_AB[1] * _R[1]) - (_AB[0] * _R[4])) > (ar + br)) { return false; }
        // Test axis L = A2 x B2
        ar = (aSize[0] * _AbsR[4]) + (aSize[1] * _AbsR[2]);
        br = (bSize[0] * _AbsR[7]) + (bSize[1] * _AbsR[6]);
        if (Mathf.Abs((_AB[1] * _R[2]) - (_AB[0] * _R[5])) > (ar + br)) { return false; }
        return true;
    }
 
    // Adapted from: http://www.mrelusive.com/publications/papers/SIMD-From-Quaternion-to-Matrix-and-Back.pdf
    private static float[] QuaternionToFloatArray(Quaternion aQuat, float[] aFlatMatrix3x3 )
    {
        if(aFlatMatrix3x3 == null) {
            aFlatMatrix3x3 = new float[9];
        }
 
        aFlatMatrix3x3[0] = 1- (2.0f * (aQuat.y * aQuat.y)) - (2.0f * (aQuat.z * aQuat.z));
        aFlatMatrix3x3[1] = (2.0f * (aQuat.x * aQuat.y)) + (2.0f * (aQuat.w * aQuat.z));
        aFlatMatrix3x3[2] = (2.0f * (aQuat.x * aQuat.z)) - (2.0f * (aQuat.w * aQuat.y));
 
        aFlatMatrix3x3[3] = (2.0f * (aQuat.x * aQuat.y)) - (2.0f * (aQuat.w * aQuat.z));
        aFlatMatrix3x3[4] = 1.0f - (2.0f * (aQuat.x * aQuat.x)) - (2.0f * (aQuat.z * aQuat.z));
        aFlatMatrix3x3[5] = (2.0f * (aQuat.y * aQuat.z)) + (2.0f * (aQuat.w * aQuat.x));
 
        aFlatMatrix3x3[6] = (2.0f * (aQuat.x * aQuat.z)) + (2.0f * (aQuat.w * aQuat.y));
        aFlatMatrix3x3[7] = (2.0f * (aQuat.y * aQuat.z)) - (2.0f * (aQuat.w * aQuat.x));
        aFlatMatrix3x3[8] = 1.0f - (2.0f * (aQuat.x * aQuat.x)) - (2.0f * (aQuat.y * aQuat.y));
 
        return aFlatMatrix3x3;
    }
}
In

Comfort is a Killer

Smoking kills. Sitting kills. Depression kills. Stress kills. Comfort kills.

Read More
In Tags

The Cosmic Joke

Felt this on the weekend on a canoeing trip.

Read More

Hacking The Rigid Body Seams Problem

A easy, hacky, solution to a troubling problem.

Read More

Custom Character Controller in Unity

The basic algorithm of a kinematic character controller is this....

Read More

Simple Object Pool For Unity

...the most expensive operations tend to be memory allocation and deallocation...

Read More
In

Simple Timer For Unity

Let's say you want something to happen 5 seconds from now...

Read More
In

Android Permissions In Unity

Starting with Android 6.0 (Marshmallow), you must request Dangerous Permissions at runtime

Read More

Cocos2D Shaders On Kindles

Cocos2D cannot run on new Kindles and the culprit is the Cocos fragment shader ccShader_PositionColorLengthTexture_frag.h.

Read More
In Tags ,

Compiling ffmpeg for Android

I've wrote a configure script to compile ffmpeg to be single threaded and static. This is a workaround to an intermittent segmentation fault inside ff_h264_remove_all_refs.

Read More
In Tags ,