Collisions, Getting the Normal of the Collision Surface (Not the Angle of Impact)

I’m working on my first Unity project right now (a bit of a self prescribed crash-course) and I’ve run into a slight issue.

I’m trying deflect one object off of another one (via collision). For now, the deflection should be at a perfectly mirrored angle. No problem! I think to myself, I see this super handy contact.normal property; seems pretty straight forward (I’m working in a 2D plane, so it really should be have been smooth sailing from there).

A few frustratingly failed attempts, and several thousand drawRay commands later, I realize that the normal is NOT a normal to the face of impact, but is a normal OF the impact (ie, mirrors the angle of impact).

This is actually useful information, but to be useful, I need a face-normal to reflect the angle of impact over.

The moving object is using this.rigidbody.velocity = so I can’t rely on built in physics; I’m also being a total stickler for how this bounce looks, so I’d really love to maintain full control over it.

Relevant code below:

function OnCollisionEnter(collision : Collision)
{
 var contactCount : int = 0;
 var normalAve : Vector3 = Vector3(0,0,0);
 var velocityAve : Vector3 = Vector3.zero;
 
 for (var contact : ContactPoint in collision.contacts)
 {
 contactCount++;
 normalAve += contact.normal;

 velocityAve += collision.relativeVelocity;
 Debug.DrawRay(contact.point, contact.normal, Color.green, 2, false);
 }
 
 normalAve /= contactCount;
 velocityAve /= contactCount;
 
 var damage = Vector3.Dot(normalAve, velocityAve) * collision.rigidbody.mass;

 
 
 life -= damage;
 
 if(life <= 0)
 {
     // Stackoverflow people can ignore this, bounces
     // don't happen when you're dead
 this.rigidbody.useGravity = this.GetComponent(ship_small_physics).stage.GetComponent(stage_params).grav_on_hit;
 
 this.GetComponent(ship_small_physics).impact();
 }else{
     // Stackoverflow people! Right here!
 Debug.Log("survived! with " + life + " life");
 this.GetComponent(ship_small_physics).small_impact(normalAve, velocityAve);
 }
}

Then, in .small_impact

function small_impact(n : Vector3, v : Vector3)
{
 var h_angle : Vector3 = Vector3(n.x, 0, n.z);
 Debug.DrawRay(this.transform.position, n, Color.blue, 2, false);
 Debug.DrawRay(this.transform.position, h_angle, Color.red, 2, false);
}

Just to clarify: The game IS in some sorts of 3D, but for various reasons in this case I only care about the collision normals in the x-z plane (another reason why the plane old physics engine won’t do). That’s why I make a new vector h_angle that’s essentially the projection of the collision-normal onto the x-z plane.

It’s entirely possible that I’m just not understanding everything.

Just had this same problem, and I came up with this solution:
You need to find one single point on the collider that you can consider as the “collision point”. This can never be completely accurate, unless you code a full-blown collision solver yourself; but what you can do is use raycasting to find some approximation.
Basically, you step away from the collision using the collision normal provided, and than raycast against the collider. RaycastHit class gives you the actual surface normal at the hit point, as well as other potentially useful info (like what triangle exactly was hit)
Here’s my code (it’s not very polished, but is generally working)

void OnCollisionEnter(Collision info){
  // find collision point and normal. You may want to average over all contacts
  var point = info.contacts[0].point;
  var dir = -info.contacts[0].normal; // you need vector pointing TOWARDS the collision, not away from it
  // step back a bit
  point -= dir;
  RaycastHit hitInfo;
  // cast a ray twice as far as your step back. This seems to work in all
  // situations, at least when speeds are not ridiculously big
  if(info.collider.Raycast( new Ray( point, dir ), out hitInfo, 2 ) )
  {
    // this is the collider surface normal
    var normal = hitInfo.normal;
    // this is the collision angle
    // you might want to use .velocity instead of .forward here, but it 
    // looks like it's already changed due to bounce in OnCollisionEnter
    var angle = Vector3.Angle( -transform.forward, normal );
  }
}