Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Is there a way to check if a vector is inside of a mesh collider?

Discussion in 'Scripting' started by Draconic, Aug 13, 2014.

  1. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    I have been spending quite a while on a custom physics system for my game, since it has very, very weird physics. I was using collider.bounds.Contains(), which worked for box colliders that were aligned defaultly, but... silly me spent 7 hours without testing it on a non - box collider.

    The physics need to work while INSIDE of a collider, so raycasts are out of the question. While inside that collider I am negating some defined areas of the collider, so whenever I test collision with my custom collision function it will check if it is in that defined area and negate it. That works fine because I just have to check distance to radius since the areas are circles. But I have come across a problem: Whenever I test collision, I create a Vector2 point alongside the player, and test if it is "colliding" with a specific type of object. collider.bounds.Contains() works fine for box colliders, but is there an equivalent for a mesh collider?

    This is the final thing to lock in place for the mechanics, so help would be really appreciated!
     
  2. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
    you could use the mesh collider bounds? Like:

    Code (CSharp):
    1.  
    2. if(collider.bounds.Contains(your vector)) {
    3.      //DoSomething
    4. }
    5.  
     
  3. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    The effect I am getting is it is detecting a box created exactly around the mesh collider. I thought that collider.bounds.Contains maybe instead created a "bounds box" around the collider, instead of being the actual collider itself. Is this not how it works? Maybe I did something else wrong...

    EDIT: I tested this in a new project, and I am sure it DOES make a box around it. Even box colliders, if I rotate them, a world aligned bounds will be created around them.
     
    Last edited: Aug 13, 2014
  4. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
    Oh sure, I've never actually tried it out so I'll take your word for it. I know you can get the hit point from ray casting but you said you didn't want to use ray casts. So you're probably going to have to write your own collision detection algorithm, As far as I'm aware there isn't another way to get the vector.

    I've done it before using this: http://wiki.unity3d.com/index.php/PolyContainsPoint

    I managed to write my own version based on that so I could use 3D mesh vertices. But thats a good starting point
     
    tqd_hshl likes this.
  5. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    Thanks! I just found exactly that page before coming to check the post. One thing I didn't really understand is, how can I take a 2D polygon, and get all of the vertices? Is there a function for it?
     
  6. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
    As far as I know there is no way of getting a sprites vertices. Only a 3D meshes.
     
  7. Graph

    Graph

    Joined:
    Jun 8, 2014
    Posts:
    153
    for a broadphase you could check the bounds of the "quad", then if contained project the vector into local space and get the matching pixel's alpha for the narrow
     
  8. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    I am using 3D meshes, and textured quads for my terrain.
     
  9. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
    Oh then that's easy. Look into the Mesh API, you can easily get a meshes vertices.

    something like this:

    Code (CSharp):
    1. Vector3[]
    2. getVertices (GameObject ObjectWithMeshFilter) {
    3.         MeshFilter mf = ObjectWithMeshFilter.GetComponent<MeshFilter> ();
    4.         return mf.mesh.vertices;
    5. }
    6.  
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    Wait... are you doing 2d or 3d?

    That link is for a 2d polygon. You're testing if a 2d point is inside the bounds of a closed polygon.

    A 3d mesh is not a 2d closed poly. Instead it's a chain of multiple 3d polys. Now... you could take that linked method and project it 2d and test that way... but you'd only be determining if the point lays exactly on the surface of a poly and is inside it. And even then... because it uses floats... it'll fail because you're off by some 0.00000000000001f units of error caused by the projecting from 3d to 2d.

    What it sounds like is that you want to know if a 3d mesh contains a 3d point (represented by a vector3).

    Well... there's a problem with that. What is "INSIDE of a collider"? When a collider is a solid like a Sphere or a Box, that definition is pretty simple. But when it's a Mesh, it's not so much. What is "INSIDE" a plane? A plane is a mesh, and it has no insides like a sphere does. Same goes for many 3d mesh surfaces. Like a height map has no "insides".

    You need to define that for a Mesh.

    For the other shapes there's simple algorithms. Just different for each. A Sphere is easy...

    Code (csharp):
    1.  
    2. public bool PointInSphere(Vector3 pnt, Vector3 sphereCenter, float sphereRadius)
    3. {
    4.     return (sphereCenter - pnt).magnitude < sphereRadius;
    5. }
    6.  
    I actually wrote up a library of geometric shapes to test 'Contains' for each. And I have methods to create a Geom struct from a Collider for doing said testing.

    Give you an example, this is a Capsule:

    Code (csharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using System.Collections.Generic;
    5.  
    6. namespace com.spacepuppy.Geom
    7. {
    8.     [System.Serializable]
    9.     public struct Capsule : IGeom, IPhysicsGeom, System.Runtime.Serialization.ISerializable
    10.     {
    11.  
    12.         #region Fields
    13.  
    14.         private Vector3 _start;
    15.         private Vector3 _end;
    16.         private float _rad;
    17.  
    18.         #endregion
    19.  
    20.         #region CONSTRUCTOR
    21.  
    22.         public Capsule(Vector3 start, Vector3 end, float radius)
    23.         {
    24.             _start = start;
    25.             _end = end;
    26.             _rad = radius;
    27.         }
    28.  
    29.         public Capsule(Vector3 center, Vector3 up, float height, float radius)
    30.         {
    31.             var h = Mathf.Max(0f,(height - (radius * 2.0f)) / 2.0f);
    32.             var change = up.normalized * h;
    33.  
    34.             _start = center - change;
    35.             _end = center + change;
    36.             _rad = radius;
    37.         }
    38.  
    39.         #endregion
    40.  
    41.         #region Properties
    42.  
    43.         public Vector3 Start
    44.         {
    45.             get { return _start; }
    46.             set { _start = value; }
    47.         }
    48.  
    49.         public Vector3 End
    50.         {
    51.             get { return _end; }
    52.             set { _end = value; }
    53.         }
    54.  
    55.         public float Radius
    56.         {
    57.             get { return _rad; }
    58.             set { _rad = value; }
    59.         }
    60.  
    61.         public float Height
    62.         {
    63.             get
    64.             {
    65.                 if (_end == _start)
    66.                     return _rad * 2.0f;
    67.                 else
    68.                     return (_end - _start).magnitude + _rad * 2.0f;
    69.             }
    70.             set
    71.             {
    72.                 var c = this.Center;
    73.                 var up = (_end - _start).normalized;
    74.                 var change = up * (value - (_rad * 2.0f));
    75.                 _start = c - change;
    76.                 _end = c + change;
    77.             }
    78.         }
    79.  
    80.         public Vector3 Center
    81.         {
    82.             get
    83.             {
    84.                 if (_end == _start)
    85.                     return _start;
    86.                 else
    87.                     return _start + (_end - _start) * 0.5f;
    88.             }
    89.             set
    90.             {
    91.                 var change = (value - this.Center);
    92.                 _start += change;
    93.                 _end += change;
    94.             }
    95.         }
    96.  
    97.         public Vector3 Up
    98.         {
    99.             get
    100.             {
    101.                 if (_end == _start)
    102.                     return Vector3.up;
    103.                 else
    104.                     return (_end - _start).normalized;
    105.             }
    106.         }
    107.  
    108.         public bool IsSpherical
    109.         {
    110.             get { return _end == _start; }
    111.         }
    112.  
    113.         #endregion
    114.  
    115.  
    116.         #region IGeom Interface
    117.  
    118.         public AxisInterval Project(Vector3 axis)
    119.         {
    120.             axis.Normalize();
    121.             var c1 = Vector3.Dot(_start, axis);
    122.             var c2 = Vector3.Dot(_end, axis);
    123.             var p1 = c1 - _rad;
    124.             var p2 = c1 + _rad;
    125.             var p3 = c2 - _rad;
    126.             var p4 = c2 + _rad;
    127.  
    128.             return new AxisInterval(axis, Mathf.Min(p1, p2, p3, p4), Mathf.Max(p1, p2, p3, p4));
    129.         }
    130.  
    131.         public Bounds GetBounds()
    132.         {
    133.             Vector3 c = this.Center;
    134.             Vector3 sz = new Vector3();
    135.             sz.x = Mathf.Abs(_start.x - c.x) + _rad;
    136.             sz.y = Mathf.Abs(_start.y - c.y) + _rad;
    137.             sz.z = Mathf.Abs(_start.z - c.y) + _rad;
    138.             return new Bounds(c, sz);
    139.         }
    140.  
    141.         public Sphere GetBoundingSphere()
    142.         {
    143.             return new Sphere(this.Center, (_end - _start).magnitude + _rad);
    144.         }
    145.  
    146.         public bool Contains(Vector3 pos)
    147.         {
    148.             var sqrRad = _rad * _rad;
    149.  
    150.             if (this.IsSpherical)
    151.             {
    152.                 return Vector3.SqrMagnitude(pos - _start) <= sqrRad;
    153.             }
    154.             else
    155.             {
    156.                 if (Vector3.SqrMagnitude(pos - _start) <= sqrRad) return true;
    157.                 if (Vector3.SqrMagnitude(pos - _end) <= sqrRad) return true;
    158.             }
    159.  
    160.             var rail = _end - _start;
    161.             var rod = pos - _start;
    162.             var sqrLen = rod.sqrMagnitude;
    163.             var dot = Vector3.Dot(rod, rail);
    164.  
    165.             if (dot < 0f || dot > sqrLen)
    166.             {
    167.                 return false;
    168.             }
    169.             else
    170.             {
    171.                 var disSqr = rod.sqrMagnitude - dot * dot / sqrLen;
    172.                 if (disSqr > sqrRad)
    173.                     return false;
    174.                 else
    175.                     return true;
    176.             }
    177.         }
    178.  
    179.         public bool Intersects(IGeom geom)
    180.         {
    181.             //TODO
    182.             throw new System.NotImplementedException();
    183.         }
    184.  
    185.         public bool Intersects(Bounds bounds)
    186.         {
    187.             //TODO
    188.             throw new System.NotImplementedException();
    189.         }
    190.  
    191.         #endregion
    192.  
    193.         #region IPhysicsGeom Interface
    194.  
    195.         public bool TestOverlap(int layerMask)
    196.         {
    197.             if (_start == _end)
    198.             {
    199.                 return Physics.CheckSphere(_start, _rad, layerMask);
    200.             }
    201.             else
    202.             {
    203.                 return Physics.CheckCapsule(_start, _end, _rad, layerMask);
    204.             }
    205.         }
    206.  
    207.         public IEnumerable<Collider> Overlap(int layerMask)
    208.         {
    209.             var hits = new List<Collider>();
    210.  
    211.             //first overlap start sphere
    212.             hits.AddRange(Physics.OverlapSphere(_start, _rad, layerMask));
    213.             //now overlap the end sphere, don't add duplicates
    214.             foreach (var c in Physics.OverlapSphere(_end, _rad, layerMask))
    215.             {
    216.                 if (!hits.Contains(c)) hits.Add(c);
    217.             }
    218.             //lastly cast from start to end, don't add duplicates
    219.             var dir = _end - _start;
    220.             var dist = dir.magnitude;
    221.             foreach (var h in Physics.SphereCastAll(_start, _rad, dir, dist, layerMask))
    222.             {
    223.                 if (!hits.Contains(h.collider)) hits.Add(h.collider);
    224.             }
    225.  
    226.             return hits;
    227.         }
    228.  
    229.         public bool Cast(Vector3 direction, out RaycastHit hitinfo, float distance, int layerMask)
    230.         {
    231.             if (_start == _end)
    232.             {
    233.                 return Physics.SphereCast(_start, _rad, direction, out hitinfo, distance, layerMask);
    234.             }
    235.             else
    236.             {
    237.                 return Physics.CapsuleCast(_start, _end, _rad, direction, out hitinfo, distance, layerMask);
    238.             }
    239.         }
    240.  
    241.         public IEnumerable<RaycastHit> CastAll(Vector3 direction, float distance, int layerMask)
    242.         {
    243.             if (_start == _end)
    244.             {
    245.                 return Physics.SphereCastAll(_start, _rad, direction, distance, layerMask);
    246.             }
    247.             else
    248.             {
    249.                 return Physics.CapsuleCastAll(_start, _end, _rad, direction, distance, layerMask);
    250.             }
    251.         }
    252.  
    253.         #endregion
    254.  
    255.  
    256.         #region ISerializable Interface
    257.  
    258.         private Capsule(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    259.         {
    260.             _start = new Vector3(info.GetSingle("start.x"), info.GetSingle("start.y"), info.GetSingle("start.z"));
    261.             _end = new Vector3(info.GetSingle("end.x"), info.GetSingle("end.y"), info.GetSingle("end.z"));
    262.             _rad = info.GetSingle("radius");
    263.         }
    264.  
    265.         void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    266.         {
    267.             info.AddValue("start.x", _start.x);
    268.             info.AddValue("start.y", _start.y);
    269.             info.AddValue("start.z", _start.z);
    270.             info.AddValue("end.x", _end.x);
    271.             info.AddValue("end.y", _end.y);
    272.             info.AddValue("end.z", _end.z);
    273.             info.AddValue("radius", _rad);
    274.         }
    275.  
    276.         #endregion
    277.  
    278.  
    279.         #region Static Interface
    280.  
    281.         public static Capsule FromCollider(CharacterController cap)
    282.         {
    283.             var cent = cap.transform.position + cap.center;
    284.             var hsc = Mathf.Max(cap.transform.lossyScale.x, cap.transform.lossyScale.y);
    285.             var vsc = cap.transform.lossyScale.y;
    286.  
    287.             return new Capsule(cent, Vector3.up, cap.height * vsc, cap.radius * hsc);
    288.         }
    289.  
    290.         public static Capsule FromCollider(CapsuleCollider cap)
    291.         {
    292.             Vector3 axis;
    293.             float hsc;
    294.             float vsc;
    295.             switch (cap.direction)
    296.             {
    297.                 case 0:
    298.                     axis = cap.transform.right;
    299.                     hsc = Mathf.Max(cap.transform.lossyScale.x, cap.transform.lossyScale.y);
    300.                     vsc = cap.transform.lossyScale.x;
    301.                     break;
    302.                 case 1:
    303.                     axis = cap.transform.up;
    304.                     hsc = Mathf.Max(cap.transform.lossyScale.x, cap.transform.lossyScale.y);
    305.                     vsc = cap.transform.lossyScale.y;
    306.                     break;
    307.                 case 2:
    308.                     axis = cap.transform.forward;
    309.                     hsc = Mathf.Max(cap.transform.lossyScale.z, cap.transform.lossyScale.y);
    310.                     vsc = cap.transform.lossyScale.z;
    311.                     break;
    312.                 default:
    313.                     return new Capsule();
    314.             }
    315.  
    316.             var cent = cap.center;
    317.  
    318.             cent = cap.transform.TransformPoint(cent);
    319.             return new Capsule(cent, axis, cap.height * vsc, cap.radius * hsc);
    320.         }
    321.  
    322.         public static Capsule FromCollider(SphereCollider sph)
    323.         {
    324.             var cent = sph.transform.TransformPoint(sph.center);
    325.             return new Capsule(cent, cent, sph.radius);
    326.         }
    327.  
    328.         #endregion
    329.  
    330.     }
    331. }
    332.  
    A portion of the geom classes are actually available on a google code project of mine. I only released a small sub-section of the full framework, so it only contains three solids.

    Sphere -
    https://code.google.com/p/spacepupp...trunk/SpacepuppyUnityFramework/Geom/Sphere.cs
    AABox -
    https://code.google.com/p/spacepupp...trunk/SpacepuppyUnityFramework/Geom/AABBox.cs
    Capsule -
    https://code.google.com/p/spacepupp...runk/SpacepuppyUnityFramework/Geom/Capsule.cs



    Again though... Meshes, that's a whole other question. As it depends on what the Mesh is.

    There are algorithms that exist for generic meshes. One is the "Separating Axis Theorem". But this only works on convex meshes. This is why Unity actually has a tick box to perform collision detection on a mesh with convex mesh only.

    You'll actually notice that the IGeom interface in that framework has a method called "Project".

    https://code.google.com/p/spacepupp.../trunk/SpacepuppyUnityFramework/Geom/IGeom.cs

    That Project method is actually used for implementing the "Separating Axis Theorem". It's just that the released stuff I have doesn't include convex mesh testing.
     
    Last edited: Aug 14, 2014
    Draconic and Graph like this.
  11. Graph

    Graph

    Joined:
    Jun 8, 2014
    Posts:
    153
    here's a quick n dirty one:
    pseudo code
    Code (CSharp):
    1. public bool isPointInVol( obj, pos) //checks if supplied position is inside supplied mesh object
    2. (
    3.     var = obj.mesh;
    4.   var nVerts=tMesh.numverts;
    5.   bool isInVol = true ;
    6.  
    7.     for v = 1 to nVerts while isInVol
    8.     if asin (dot (getNormal tMesh v) (normalize(((getVert tMesh v)*obj.transform) - pos))) <= 0.0
    9.       isInVol = false ;
    10.    
    11.   tMesh = nVerts = vPos = undefined ;
    12.  
    13.     return isInVol ;
    14. )
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,500
    Actually, it's not that complex as long as your mesh fits a few criteria:
    - It's a sealed volume
    - It's convex (or is split up into convex sections which you check individually)
    - No edge is shared by more than two faces

    While that sounds awfully specific, what it boils down to is "it has to be a mesh where the concepts of inside and outside actually make sense". (If a mesh doesn't fit any one of those rules there's no clear "inside".)

    Assuming those things are true, all you need to do is check for each face that the desired vector is on the "inside" side, and that's math you can look up in any number of game dev or math resources.
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    Ummm... yeah.

    That's pretty much exactly what I was saying. My point of talking about a plane or height map was that they're examples of things that aren't solid geometry (sealed volumes). That's why later in the post I specifically said mentioned convex hulls in regards to the "Separating Axis Theorem".
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,500
    Yeah, I thought that the half of the thread I hadn't got to might have got to that stuff, but I only had a moment before I had to duck out and thought it better to mash out something potentially helpful than to not.

    Better to have it said twice than not to have it said at all. ;)