Search code examples
c#unity-game-enginegame-physicsphysics-enginephysx

Get point of collision from Physics.ComputePenetration()


I have a very basic player in Unity that I coded to collide against objects using Physics.ComputePenetration(...) and it works relatively well. The problem is that I would like more detailed information about where the player hit the collider.

Some things to note:

First of all, the idea of this project is to see if I can replicate the CharacterController's functionality from scratch using ComputePenetration. I am currently trying to replicate the built-in OnControllerColliderHit(...) function, which allows me to get the location of where the hit took place.

Second of all, I am not using a SphereCollider. If I was, then all I would have to do is get the returned direction vector and multiply it by the sphere's radius (along with a couple of other things). But sadly, I am using a CapsuleCollider (maybe there's a way to do the same thing but for a CapsuleCollider, but I have not been able to find one).

What I've tried:

My initial idea was to take the collider that the player hit, hitCollider, and execute this snippet of code:

Vector3 point = hitCollider.ClosestPoint(player.transform.position);

to get the closest point on the hit collider. From there, I could call

Vector3 finalPoint = player.collider.ClosestPoint(point);

on the player to get its closest point to the hit collider's closest point. It's very confusing, but it works pretty well. There's only one drawback, however: I plan on using ProBuilder to do a lot of my level design, and you cannot call ClosestPoint on a MeshCollider that does not have convex set to true, which is a majority of meshes in ProBuilder.

I thought of a couple of other things, like maybe I could invert the direction and then do some (not-so) hacky math to get the contact point, etc. but nothing seemed to work as a full-blown solution.

Closing thoughts:

I'm beginning to think that it's not technically possible. Which is true if you think about it, because when two objects intersect, there's no single point that defines it, it's an entire volume that does so. But getting the average position of that volume would (maybe?) be the solution.

Any ideas?

Thank you in advance!


Solution

  • For anyone else who was curious, I found the solution. When Physics.ComputePenetration(...) between ourCollider and selectedCollider detects a collision, make sure to de-penetrate FIRST, then do the following:

    Call a Physics.CapsuleCast(...), but make sure that point1, point2, and radius are slightly smaller values (maybe 0.001) than the original capsule's size. This is because the CapsuleCast will think that it's intersecting with the selectedCollider and will therefore just go straight through it, which you don't want. Then just make your direction the negative direction that was output from Physics.ComputePenetration(...).

    So your code ends up looking like this:

    var origin = this.transform.position + this.center;
    var offset = new Vector3(0.0f, this.height / 2.0f - this.radius - someTinyValue, 0.0f);
    if (Physics.CapsuleCast(
            origin + offset,
            origin - offset,
            this.radius - someTinyValue,
            -direction,
            out var hit))
    {
        // Do whatever with the `hit` variable
    }