- Home /
Getting surface normal from collision
When a collision event is triggered, you are returned a list of contact points, from which you can get normals. Unity's manual describes this as the "Normal of the contact point." Not very descriptive. I have always assumed this was the surface normal of the opposite collider at the point of collision, however this does not seem to be the case.
ContactPoint.normal doesn't seem to give the actual surface normal, but rather some kind of impact normal. In other words, depending on the angles of impact, the normal returned can change.
I did a test with a sphere collider and a ramp (rotated flattened box collider). The ramp was rotated to have a slope of 45 degrees. I made the sphere collider "walk up" the ramp and output the slope of the surface derrived from the contact normal as it pushed its way up the ramp. At first the results were as expected -- returned angle was 45 degrees. Then I disabled and re-enabled the sphere collider game object and the results changed. The impact returned different values now. The angle compared to Vector3.up would jitter +- 10 degrees as the sphere pushed up against the ramp. Visualizing this with a short ray at the point of contact showed a line that danced slighly on the vertical axis.
The only reason I noticed this was because I tried my collision with another test sphere I was using for doing some character movement tests. This sphere was never able to get the stable, smooth 45 degree result returned like my simpler sphere above. When pushing against the ramp, I'd get about a 50+ degree angle returned, but when I stopped pushing against the ramp, it would drop back to the correct 45 degrees. Every time I pushed up against the ramp, I'd see the incorrect slope angle returned.
The only way I've found to reliably get the surface normal is with a raycast. Am I missing something here? It sure would be a lot simpler if ContactPoint.normal just returned the surface normal of the polygon it collided with. I guess you could also get the surface normal from a mesh collider by getting the triangle list and normal list, but this seems overly complicated.
What do you mean by "disabled and re-enabled the sphere collider game object"? $$anonymous$$anually in the inspector? Or via script?
It doesn't seem to change anything for me. Was trying to reproduce your jitter without success. The normals are fine, even when I apply any force to the objects.
Re: disabling/enabling. $$anonymous$$anually in the inspector. If you do the test below and everything comes out fine, try disabling and renabling the sphere object in the inspector while it's playing and try again.
Here's a little test script that should show what I mean. To set up the object, create a game object with a sphere collider and a rigidbody (disable useGravity, enable freeze rotation XYZ), add the script. Create several ramps and different slope angles, and even rotate a couple on the Y so they're not all facing XY perfectly. $$anonymous$$ove the game object with the WASD keys, RF for up/down movement. Enable gizmo display. Play and push the sphere against the ramp at various angles. Try stopping on the ramp. It will show a debug readout showing the current slope angle of the surface as well as draw a red line representing the surface normal of the contact.
using UnityEngine;
using System.Collections;
public class CollisionTest : $$anonymous$$onoBehaviour {
public float speed = 5.0f;
private Vector3 moveDirection = Vector3.zero;
private Rigidbody rb;
void Awake() {
rb = rigidbody;
}
void OnCollisionStay(Collision c) {
// Display slope of all collision contacts
ContactPoint[] ps = c.contacts;
for(int i = 0; i < ps.Length; i++) {
Vector3 normal = ps[i].normal;
Debug.DrawRay(ps[i].point, normal, Color.red);
float slopeAngle = Vector3.Angle(normal, Vector3.up);
Debug.Log("contact.normal slope = " + slopeAngle + " degrees @ " + Time.time); // show the surface normal
//Debug.Break();
}
}
void FixedUpdate() {
// zero moveDirection
moveDirection.x = 0.0f;
moveDirection.y = 0.0f;
moveDirection.z = 0.0f;
// zero velocity - critical to eli$$anonymous$$ate physics imparted forces (trying to mimic CharacterController's behavior)
rb.velocity = new Vector3(0.0f, 0.0f, 0.0f);
// add unit movement
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.W))
Add$$anonymous$$ove(Vector3.left);
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.A))
Add$$anonymous$$ove(Vector3.back);
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.D))
Add$$anonymous$$ove(Vector3.forward);
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.S))
Add$$anonymous$$ove(Vector3.right);
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.R))
Add$$anonymous$$ove(Vector3.up);
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.F))
Add$$anonymous$$ove(Vector3.down);
// move the unit
rb.AddForce(moveDirection, Force$$anonymous$$ode.VelocityChange);
}
void Add$$anonymous$$ove(Vector3 dir) {
Vector3 move = dir * speed;
moveDirection.x += move.x;
moveDirection.y += move.y;
moveDirection.z += move.z;
}
}
Very interesting, now I can reproduce your error - but only when I briefly disable the object. Disabling and enabling seems to somehow break the rigidbody. When you play long enough with the rigidbody settings it does recover again. Really strange. It's definitely not expected behavior. I would file a bug report.
In the meantime your raycast solution seems the best way to go.
sounds mysterious-good to file a bug report! Just take the scene in question and send it to unity as a package if you want or use the in-program bug report option.
you could disable the mesh renderer, the Collider component, and other components individually to achieve the same thing? you could also change the ball position and place it in outer space when you don't need it?
Glad to see it does the same thing for you and I'm not crazy. ;) Actually, in my case, I don't even have to disable/enable the object for the problem to show up. So while there may be some kind of bug with enable/disable, it's possible to get this issue without doing that.
I ran into another situation where the normal returned is incorrect. I placed a flat rectangular step (about 0.15 high) sticking out of the flat plane floor to test walking up a step simply using the default behavior of physics forces. I ran the sphere collider towards the step and placed a Debug.Break and a Debug.DrawLine showing the normal on impact of the most forward collision point. One would expect the normal to return a 90 degree slope if it hit the side plane, or a 0 degree slope if it hit the top plane. However, it returned slopes between 13 and 40 something degrees randomly. The best I could tell is that it may have been returning an average normal since it was essentially hitting the corner of the step, though I don't recall ever seeing exactly 45 degrees. Still, it reenforces my concern that CollisionPoint.normal does not return surface normals but rather some other normal based on impact angles, or the shapes of the colliders, or something I haven't identified.
Answer by GerryM · Feb 17, 2013 at 01:30 PM
Anyhow, apart from what CollisionPoint.normal should return, it doesn't return anything consistently. Therefore it seems to be buggy.
Please, file a bug report with an easy example and keep us posted on any news. Thank you!
Your answer
Follow this Question
Related Questions
Collision.contacts? 1 Answer
Obtaining contact point and normal of first object in collision 0 Answers
contact.normal not updating in OnCollisionStay2D 0 Answers
Collision normals changing based on player position 0 Answers
How do I get a collision's contact normal without using any non-kinematic rigidbodies? 0 Answers