- Home /
How do I obtain the surface normal for a point on a collider (can't use RaycastHit.normal)?
Is there an alternate way of obtaining the true surface normal given a point on a collider surface other than RaycastHit.normal?
It seems as if RaycastHit.normal is bugged and doesn't always return a surface normal, but something else.
Answer by Statement · Mar 06, 2011 at 03:55 PM
I don't think the fault is in the RaycastHit.normal but rather the call that populates it (SphereCast). However, if you get the triangle you hit, you can use the hit.barycentricCoordinate to calculate the surface normal. This will only work on mesh colliders as only mesh colliders can provide mesh data.
private Vector3 GetMeshColliderNormal(RaycastHit hit) { MeshCollider collider = (MeshCollider)hit.collider; Mesh mesh = collider.sharedMesh; Vector3[] normals = mesh.normals; int[] triangles = mesh.triangles;
Vector3 n0 = normals[triangles[hit.triangleIndex * 3 + 0]];
Vector3 n1 = normals[triangles[hit.triangleIndex * 3 + 1]];
Vector3 n2 = normals[triangles[hit.triangleIndex * 3 + 2]];
Vector3 baryCenter = hit.barycentricCoordinate;
Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z;
interpolatedNormal.Normalize();
interpolatedNormal = hit.transform.TransformDirection(interpolatedNormal);
return interpolatedNormal;
}
I tested this code right after your previous question with the sphere cast. It seems to work well with the plane but the position of the sphere cast hit is still arbitrary with regard to large radii.
Answer by poncho · Mar 08, 2011 at 05:21 PM
i use the
void OnCollisionEnter(Collision collisionInfo)
{
}
the normal vector3 can be obtained really easy just need this
collisionInfo.contacts[0].normal;
i used this code on a bouncing ball on irregular planes, really useful this simplify the need of the GetMeshColliderNormal, it have all that code inside
hope this helps as it helped me =)
Just going to point out for anyone reading that the normal given by
ContactPoint.normal;
(which is the result of collisionInfo.contacts[0].normal;) is not the same as the normal given by
RaycastHit.normal;
The former is the normal of the collision itself - ie, the direction the that two objects want to push away from each other. The latter is the normal of the surface being collided with.
For a bouncing ball, ContactPoint.normal makes sense. But for finding the surface normal, it doesn't.
Answer by FeastSC2 · Mar 14 at 01:42 PM
It's an old thread, but I stumbled upon it and it did not provide the answer I was looking for.
Raycasting will get you the expected normals, but if you're using SphereCast or CapsuleCast, the normals that are given by these functions are not the normals you expect to receive. The solution to this is simply to then do a Raycast to this collider to acquire the surface normal (as opposed to the collision normal). This works for EVERY type of collider except the Non-convex mesh collider. In that scenario, you could use the solution provided by user @Statement, however I do not recommend it. Your game needs and should only have convex mesh colliders, that's just the unfortunate reality. There are some assets in the store that making non-convex mesh colliders into multiple convex mesh colliders.
public static RaycastHit CreateRaycastHitFromCollider(Vector3 _rayOrigin, Collider _collider)
{
var colliderTr = _collider.transform;
// Returns a point on the given collider that is closest to the specified location.
// Note that in case the specified location is inside the collider, or exactly on the boundary of it, the input location is returned instead.
// The collider can only be BoxCollider, SphereCollider, CapsuleCollider or a convex MeshCollider.
var closestPoint = Physics.ClosestPoint(_rayOrigin, _collider, colliderTr.position, colliderTr.rotation);
if (_collider is MeshCollider {convex: false} meshCollider)
{
Debug.LogWarning($"do not use convex mesh-colliders as it does not deal well with physics at all. " +
$"There are solutions provided in the asset store to automatically transform non-convex meshes to convex meshes. The problematic mesh: {_collider.transform.GetFullPathName()} meshName:{meshCollider.sharedMesh.name}");
// This is not great. If we have complex meshColliders we will encounter issues.
closestPoint = _collider.ClosestPointOnBounds(_rayOrigin);
}
var dir = (closestPoint - _rayOrigin).normalized;
var ray = new Ray(_rayOrigin, dir);
var hasHit = _collider.Raycast(ray, out var hitInfo, float.MaxValue);
if (hasHit == false)
{
Debug.LogError($"This case will never happen!");
}
return hitInfo;
}
Thanks for this!
We can also add this at the beginning of your function to avoid errors if we try to test from INSIDE the collider for even a moment. For now we just return a useless rayHit...
RaycastHit hitInfo = new RaycastHit();
bool inside;
inside = _collider.bounds.Contains(_rayOrigin);
if (inside)
{
Debug.LogWarning("Getting raycast to nearest point failed - we are inside provided collider");
return hitInfo;
}