- Home /
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[tt].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);
}
Answer by Benjames · Feb 12, 2019 at 05:14 PM
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[tt].collider == other) {
counter++;
from = hit[tt].point+dir.normalized*.001f;
dist = Vector3.Distance(from, to);
Break = false;
break;
}
}
}
return (counter);
}
Answer by Ymrasu · Feb 11, 2019 at 06:46 PM
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;
}
I believe that it would not work out for all shapes. like a concave shape where the center of the bounds doesn't actually lie within the shape. Thanks for the input though!
This is what happens when I overthink it. I think you can simply check if (other.ClosestPoint(point) == point) to see if it is inside the mesh.
Oh jeez I didn't know that existed. I'll try it out thanks!
Answer by olejuer · Feb 19, 2020 at 11:04 AM
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[i].normal.y > 0)
for (var j = 0; j < hitsDown; j++)
if (_hitsDown[j].normal.y < 0 && _hitsDown[j].collider == _hitsUp[i].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.
Answer by Laiken · Dec 23, 2021 at 07:22 AM
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;
}
}
Your answer