Point inside mesh?

I have this function that is supposed to check if a point is inside a mesh collider. The problem is it doesn’t work right all the time. It actually works better is I change Vector3 from= (Vector3.up *5000f); to Vector3 from = (Random.onUnitSphere * 5000f);… but that doesn’t seem very consistent, like it works but is bad practice right?

    bool IsInCollider(MeshCollider other, Vector3 point) {
        Vector3 from = (Vector3.up * 5000f);
        Vector3 dir = (point - from).normalized;
        float dist = Vector3.Distance(from, point);
        int hit_count = 0;
        //fwd
        RaycastHit[] hit = Physics.RaycastAll(from, dir, dist);
        for (int tt = 0; tt < hit.Length; tt++) {
            if (hit
.collider == other) {
                    hit_count++;
                }
            }
            //back
            dir = (from - point).normalized;
            RaycastHit[] hitt = Physics.RaycastAll(point, dir, dist);
            for (int tt = 0; tt < hitt.Length; tt++) {
                if (hitt[tt].collider == other) {
                    hit_count++;
                }
            }
            if (hit_count % 2 == 1) {
                return (true);
            }
            return (false);
        }

For mesh colliders

bool IsInsideMeshCollider(MeshCollider col, Vector3 point)
    {
        var temp = Physics.queriesHitBackfaces;
        Ray ray = new Ray(point, Vector3.back);

        bool hitFrontFace = false;
        RaycastHit hit = default;

        Physics.queriesHitBackfaces = true;
        bool hitFrontOrBackFace = col.Raycast(ray, out RaycastHit hit2, 100f);
        if (hitFrontOrBackFace)
        {
            Physics.queriesHitBackfaces = false;
            hitFrontFace = col.Raycast(ray, out hit, 100f);
        }
        Physics.queriesHitBackfaces = temp;

        if (!hitFrontOrBackFace)
        {
            return false;
        }
        else if (!hitFrontFace)
        {
            return true;
        }
        else 
        {
            // This can happen when, for instance, the point is inside the torso but there's a part of the mesh (like the tail) that can still be hit on the front
            if (hit.distance > hit2.distance)
            {
                return true;
            }
            else
                return false;
        }

    }

I believe Physics.Raycast only hits colliders it does not start within. So you can use that to your advantage. If you cast from your point towards the collider it should hit it, unless it is within the collider.

bool IsInCollider(MeshCollider other, Vector3 point)
	{
		Vector3 direction = other.bounds.center - point;
		RaycastHit[] hits = Physics.RaycastAll(point, direction);

		foreach(RaycastHit hit in hits) {
			if(hit.collider == other) {
				// we hit it so we were outside it
				return false;
			}
		}
		
		// no hits probably means we're inside it
		return true;
	}

EDIT: Thinking about another way to get a point inside the mesh instead of other.bounds.center made me realize that you can just try this:

bool IsInCollider(Collider other, Vector3 point)
	{

		if(other.ClosestPoint(point) == point) {
			return true;
		}
		
		return false;
	}

Ok so I found that RaycastAll will only return the first hit it gets on a single mesh. So I had to write something that restarted the Raycast from the hit position. Here is my code.

   public  bool IsInCollider(MeshCollider other, Vector3 point) {
        Vector3 from = (Vector3.up * 5000f);
        Vector3 dir = (point - from).normalized;
        float dist = Vector3.Distance(from, point);        
        //fwd      
        int hit_count = Cast_Till(from, point, other);
        //back
        dir = (from - point).normalized;
        hit_count += Cast_Till(point, point + (dir * dist), other);

        if (hit_count % 2 == 1) {
            return (true);
        }
        return (false);
    }

    int Cast_Till(Vector3 from, Vector3 to, MeshCollider other) {
        int counter = 0;
        Vector3 dir = (to - from).normalized;
        float dist = Vector3.Distance(from, to);
        bool Break = false;
        while (!Break) {
            Break = true;
            RaycastHit[] hit = Physics.RaycastAll(from, dir, dist);
            for (int tt = 0; tt < hit.Length; tt++) {
                if (hit
.collider == other) {
                        counter++;
                        from = hit[tt].point+dir.normalized*.001f;
                        dist = Vector3.Distance(from, to);
                        Break = false;
                        break;                    
                    }
                }
            }
            return (counter);
        }

Very old thread, but came up on google when looking for “Unity check inside mesh”. This is actually quite a tricky problem, but I have a simple solution that covers most cases

private bool IsInsideMesh(Vector3 point)
    {
        Physics.queriesHitBackfaces = true;
        int hitsUp = Physics.RaycastNonAlloc(point, Vector3.up, _hitsUp);
        int hitsDown = Physics.RaycastNonAlloc(point, Vector3.down, _hitsDown);
        Physics.queriesHitBackfaces = false;

        for (var i = 0; i < hitsUp; i++)
            if (_hitsUp*.normal.y > 0)*

for (var j = 0; j < hitsDown; j++)
if (_hitsDown[j].normal.y < 0 && _hitsDown[j].collider == _hitsUp*.collider)*
return true;

return false;
}
This makes use of the fact that no collider is hit twice by a raycast. If the hit normal has a positive component in the direction of the raycast, it’s a backface. As long as the mesh has a closed surface and does not extend indefinitely, a single raycast would suffice. Any quad, or plane, however, would give false positives. To cover those cases, fire a raycast in the opposite direction and see if you hit a backface of the same mesh. This covers most cases.
If you look closely, though, things get tricky and mathy. There meshes that generate false positives, like an open half sphere. You would have to check, if the mesh is a closed surface. Another problem is that the mesh could have a closed part and an open part, like a cube with an extra face extruded from one edge. The point could be inside the closed part, but the full mesh would technically still not be a closed surface, giving you a false negative. So you should limit the check to the part of the mesh that you hit with the raycast.
So yeah, there should be an asset for this…
Personally, I will just live with the check above.

Four solutions:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// To give proper results, mesh must be closed (have volume) !
public class PointInMesh : MonoBehaviour
{
	[Header("Input point")]
	public Transform Source; // input point
	[Header("Destination Mesh")]
	public Transform Destination; // destination mesh transform
	[Header("Select 1 || 2 || 3 || 4")]
	public int Function = 1;

	private Vector3[] _Vertices;
	private int[] _Triangles;
	private MeshCollider _MeshCollider;

	// Möller–Trumbore ray-triangle intersection algorithm: http://www.graphics.cornell.edu/pubs/1997/MT97.pdf 
	// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
	// ro = ray origin
	// rd = ray direction
	// a,b,c = triangle vertices in world space
	// hit = optionally get ray hit position in world space	
	bool RayTriangleIntersection (Vector3 ro, Vector3 rd, Vector3 a, Vector3 b, Vector3 c, out Vector3 hit)
	{
		float epsilon = 0.0000001f;
		hit = new Vector3(0f, 0f, 0f);
		Vector3 ba = b - a;
		Vector3 ca = c - a;
		Vector3 h = Vector3.Cross(rd, ca);
		float det = Vector3.Dot(ba, h);
		if (det > -epsilon && det < epsilon) return false;
		float f = 1.0f / det;
		Vector3 s = ro - a;
		float u = Vector3.Dot(s, h) * f;
		if (u < 0.0f || u > 1.0f) return false;
		Vector3 q = Vector3.Cross(s, ba);
		float v = Vector3.Dot(rd, q) * f;
		if (v < 0.0f || u + v > 1.0f) return false;
		float t = Vector3.Dot(ca, q) * f;
		hit = ro + rd * t;
		return (t > epsilon);
	}

	// First method:
	// Create a ray (infinite line starting at input point and going in some random direction).
	// Find intersections between ray and all mesh triangles. An odd number of intersections means it is inside the mesh.
	// position = input point in world space
	// transform = mesh transform
	// vertices = mesh vertices
	// triangles = mesh triangles (indices)
	bool IsPointInsideMesh (Vector3 position, Transform transform, Vector3[] vertices, int[] triangles)
	{
		Vector3 epsilon = new Vector3(0.001f, 0.001f, 0.001f);
		Vector3 direction = Vector3.Normalize(Random.insideUnitSphere + epsilon);
		int intersections = 0;
		for (int i = 0; i < triangles.Length; i += 3)
		{
			Vector3 a = transform.TransformPoint(vertices[triangles[i + 0]]);
			Vector3 b = transform.TransformPoint(vertices[triangles[i + 1]]);
			Vector3 c = transform.TransformPoint(vertices[triangles[i + 2]]);
			intersections += RayTriangleIntersection(position, direction, a, b, c, out Vector3 hit) ? 1 : 0;
		}
		return (intersections % 2 == 1);
	}

	// Second method:
	// Create a ray (infinite line starting at input point and going in some random direction).
	// Find the closest intersection between ray and all mesh triangles.
	// If triangle in the closest intersection is hit to back face, it means input point is inside the mesh.
	// transform = mesh transform
	// vertices = mesh vertices
	// triangles = mesh triangles (indices)	
	// position = input point in world space
	bool IsPointInsideMesh (Transform transform, Vector3[] vertices, int[] triangles, Vector3 position)
	{
		Vector3 epsilon = new Vector3(0.001f, 0.001f, 0.001f);
		Vector3 direction = Vector3.Normalize(Random.insideUnitSphere + epsilon);
		float closestDistance = 1e9f;
		bool isBackFace = false;
		for (int i = 0; i < triangles.Length; i += 3)
		{
			Vector3 a = transform.TransformPoint(vertices[triangles[i + 0]]);
			Vector3 b = transform.TransformPoint(vertices[triangles[i + 1]]);
			Vector3 c = transform.TransformPoint(vertices[triangles[i + 2]]);
			if (RayTriangleIntersection(position, direction, a, b, c, out Vector3 hit))
			{
				float currentDistance = Vector3.Distance(hit, position);
				if (currentDistance < closestDistance)
				{
					Vector3 ba = b - a; 
					Vector3 ca = c - a;
					Vector3 triangleNormal = Vector3.Normalize(Vector3.Cross(ba, ca));
					closestDistance = currentDistance;
					isBackFace = Vector3.Dot(direction, triangleNormal) > 0f ? true : false;
				}
			}
		}
		return isBackFace;
	}

	// Third method:
	// RaycastAll returns hits of all colliders hit, but per collider only the closest one.
	// So we need to use while loop to trace current position and count number of intersections.
	// An odd number of intersections means it is inside the mesh.
	// position = input point in world space
	// collider = mesh collider
	bool IsPointInsideCollider (Vector3 position, MeshCollider collider)
	{
		Physics.queriesHitBackfaces = true;
		Vector3 epsilon = new Vector3(0.001f, 0.001f, 0.001f);
		Vector3 direction = Vector3.Normalize(Random.insideUnitSphere + epsilon);
		int intersections = 0;
		bool exit = false;
		while (!exit)
		{
			exit = true;
			RaycastHit[] hits = Physics.RaycastAll(position, direction);
			for (int i = 0; i < hits.Length; i++)
			{
				if (hits*.collider == collider)*
  •  		{*
    

position = hits_.point + direction * 0.001f;_
* intersections++;*
* exit = false;*
* break;*
* }*
* }*
* }*
* Physics.queriesHitBackfaces = false;*
* return (intersections % 2 == 1);*
* }*

* // Triangle Signed Distance Function*
* // https://iquilezles.org/articles/distfunctions/*_
* float TriangleSDF (Vector3 p, Vector3 a, Vector3 b, Vector3 c)*
* {*
* Vector3 ba = b - a;*
* Vector3 pa = p - a;*
* Vector3 cb = c - b;*
* Vector3 pb = p - b;*
* Vector3 ac = a - c;*
* Vector3 pc = p - c;*
* Vector3 nm = Vector3.Cross(ba, ac);*
* float sa = Mathf.Sign(Vector3.Dot(Vector3.Cross(ba, nm), pa));*
* float sb = Mathf.Sign(Vector3.Dot(Vector3.Cross(cb, nm), pb));*
* float sc = Mathf.Sign(Vector3.Dot(Vector3.Cross(ac, nm), pc));*
Vector3 va = ba * Mathf.Clamp(Vector3.Dot(ba, pa) / Vector3.Dot(ba, ba), 0.0f, 1.0f) - pa;
Vector3 vb = cb * Mathf.Clamp(Vector3.Dot(cb, pb) / Vector3.Dot(cb, cb), 0.0f, 1.0f) - pb;
Vector3 vc = ac * Mathf.Clamp(Vector3.Dot(ac, pc) / Vector3.Dot(ac, ac), 0.0f, 1.0f) - pc;
* float m1 = Mathf.Min(Mathf.Min(Vector3.Dot(va, va), Vector3.Dot(vb, vb)), Vector3.Dot(vc, vc));*
float m2 = Vector3.Dot(nm, pa) * Vector3.Dot(nm, pa) / Vector3.Dot(nm, nm);_

* return Mathf.Sqrt(sa + sb + sc < 2.0 ? m1 : m2);*
* }*

* // Fourth method:*
* // Calculate signed distance field from triangle mesh.*
* // Distance between point located inside mesh and the closest triangle has negative sign.*
* bool IsPointInsideMesh (Vector3 position, Transform transform, Vector3[] vertices, int[] triangles, out float minimalDistance)*
* {*
* minimalDistance = 1e9f;*
* float currentSign = 0.0f;*
* for (int i = 0; i < triangles.Length; i += 3)*
* {*
* Vector3 a = transform.TransformPoint(vertices[triangles[i + 0]]); // from local space to world space*
* Vector3 b = transform.TransformPoint(vertices[triangles[i + 1]]); // from local space to world space*
* Vector3 c = transform.TransformPoint(vertices[triangles[i + 2]]); // from local space to world space*
* Vector3 ba = b - a;*
* Vector3 ca = c - a;*
* float currentDistance = TriangleSDF (position, a, b, c);*
* if (currentDistance < minimalDistance)*
* {*
* Vector3 triangleNormal = Vector3.Normalize(Vector3.Cross(ba, ca));*
* Vector3 triangleCenter = (a + b + c) / 3.0f;*
* Vector3 direction = Vector3.Normalize(triangleCenter - position);*
_ currentSign = -1.0f * System.Math.Sign(Vector3.Dot(direction, triangleNormal));
* minimalDistance = currentDistance;*
* }*
* }*
minimalDistance = minimalDistance * currentSign;_

* return currentSign < 0.0f;*
* }*

* void Start()*
* {*
* Mesh mesh = Destination.gameObject.GetComponent().sharedMesh;*
* _Vertices = new List(mesh.vertices).ToArray();
_Triangles = new List(mesh.triangles).ToArray();
MeshCollider = Destination.gameObject.GetComponent();
_
}*

* void Update()*
* {*
* switch (Function)*
* {*
* case 1:*
* Debug.Log(IsPointInsideMesh(Source.position, Destination, _Vertices, Triangles));
_
break;*

* case 2:*
* Debug.Log(IsPointInsideMesh(Destination, _Vertices, Triangles, Source.position));
_
break;*

* case 3:*
* Debug.Log(IsPointInsideCollider (Source.position, MeshCollider));
_
break;*

* case 4:*
* Debug.Log(IsPointInsideMesh(Source.position, Destination, _Vertices, Triangles, out float sdf));
_
break;*

* }*
* }*
}