Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Custom SweepTest Collidion Detection hELP

Discussion in 'Scripting' started by techmage, Aug 22, 2013.

  1. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    This is driving me crazy, I'm seriously at like crazy frantic pacing and smoking too much trying to make this work, I need some help.

    I am trying to implemented basically the same functionality as CharacterController.Move through a Rigidbody with a box collider and RigidBody.SweepTest

    In this specific application, this object must be moved in a very precise manner and respond differently to what it hits. So I need to be able to set exact transforms, and know what it is hitting in the Update loop. Which is why I am trying to implement this via sweepTest.

    I have it mostly worked out. The thing that is driving me crazy is how to make it so that it doesn't just stick to walls, but rather slide along them when a user pushes an object up against the wall.
     
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    So something like this

    your input vector is this, but your sweeptest has determined that the player will hit the wall if you call your move method, so instead, you pass the desired direction to the move method

    the desired direction should be perpendicular to the surface normal
    Then you can reduce the desired vector (like friction) by multiplying by something like 1 - ( the dot product of backwards input and suface normal vectors )
     
  3. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Thank you for that. That helped spawned a line of thought that got me a bit farther.

    My issue is a little different than what that solves though. It's not simply enough for it to slow down when sliding against a wall. But based on user input, clicking on the screen, it needs to slide against the wall being exactly in line with the cursor. If that makes any sense.

    So I can't just slow it down as it slides against the wall, I need to calculate exactly how far it needs to slide to the right to stay perfectly lined up with the cursor.

    I've actually managed to do this now, but my solution requires doing two SweepTests. First the initial SweepTest to see if the initial move will hit anything. Which if it hits it moves it to where it hit. Then through some weird function I put together that does some clever stuff with Rays and Planes, it figures out exactly how much it needs to slide it along the wall to line up with the pointer. Which at that point it needs to do another SweepTest to ensure this second translation along the wall isn't putting it through a wall. So it takes two sweepTests and some weird stuff with Planes and Rays.

    I feel like there has to be an easier way... like some 3D math function that should be able to do this alot simpler... but I don't know that much about that. Is there any way to pull this off simpler than using two SweepTests and some clever tricks with Rays and Planes?
     
  4. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    So I am posting my solution here for someone else to use, and maybe for someone else to look over and potentially improve.

    I have basically broken this down to a RigidBody.Move function extension so it works really close to CharacterController.Move

    You call .Move on the rigidBody you want to move.
    Then you must pass it the Transform attached to the rigidBody, this is done so you don't have to do a .transform lookup on the rigid body.
    Then you pass it newPos, which is just the newPos of where its supposed to go, exactly how CharacterController.Move works.
    Then you put in a tolerance, basically before it does it's SweepTesting it moves the RigidBody backwards in units by the tolerance you specify, then does the SweepTest from there. This is done because sometimes if you are dragging an object right up against a wall it can kind of wiggle it's way through the wall, so you move it back a little from the wall before sweep testing so it properly detects the wall. Typically I am find a value of .02f is working out well. You do not want this tolerance to be bigger than the actual bound of your object (I think).
    Then you put in an angleTolerance, which isn't named too well, I couldn't of a better name. Something when you are dragging it smashed up against a wall tight, the second sweepTest that test parallel to the wall will return hits exactly perpindicular to the direction your object is moving, making it behave weirdly, this is a tolerance for it to ignore those hits. It makes it so the sweepTest hit must be facing the direction of the move by the specified amount for it to hit. I've been using 45, this seems to work well, but might need some playing with.

    This seems to be working good and reliably MOST of the time. If you drag it in some areas it kind of glitches a bit. I'm hoping maybe some more people looking at it can refine it.

    Also I used the 3D math functions from here:
    http://wiki.unity3d.com/index.php?title=3d_Math_functions
    Which you will need to put in your project, although I renamed that class to Math3D instead of Math3d cause I didn't like the lowercase d. Also the Math3D class has an error on it where the very last function needs to be made static and moved inside of the static class.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. static public class Extension
    6. {
    7.     static public void Move(this Rigidbody rigidBody, Transform transform, Vector3 newPos, float tolerance, float angleTolerance)
    8.     {
    9.         RaycastHit raycastHit;
    10.        
    11.         Vector3 sweepDirection = newPos - transform.position;
    12.         Vector3 sweepDirectionNormalized = sweepDirection.normalized;
    13.        
    14.         transform.Translate(-sweepDirectionNormalized * tolerance ); //go back a step to give some tolerace
    15.        
    16.         if (rigidBody.SweepTest(sweepDirectionNormalized,out raycastHit,sweepDirection.magnitude+tolerance))
    17.         {      
    18.             transform.Translate(sweepDirectionNormalized*raycastHit.distance,Space.World);
    19.            
    20.             Vector3 linePoint;
    21.             Vector3 lineVec;
    22.             Vector3 raycastHitPerpindicular = Vector3.Cross(raycastHit.normal,Vector3.up);
    23.            
    24.             Math3D.PlanePlaneIntersection(out linePoint,out lineVec,raycastHit.normal,transform.position,raycastHitPerpindicular,newPos);
    25.            
    26.             sweepDirection = linePoint - transform.position;
    27.             sweepDirectionNormalized = sweepDirection.normalized;
    28.            
    29.             transform.Translate(-sweepDirectionNormalized * tolerance ); //go back a step to give some tolerace
    30.            
    31.             Debug.DrawRay(transform.position,sweepDirection);
    32.            
    33.             if (rigidBody.SweepTest(sweepDirectionNormalized,out raycastHit,sweepDirection.magnitude+tolerance))
    34.             {  
    35.                 Debug.DrawRay(raycastHit.point,raycastHit.normal,Color.magenta);
    36.                 if (Vector3.Angle(-raycastHit.normal, sweepDirectionNormalized) < angleTolerance)
    37.                     transform.Translate(sweepDirectionNormalized*raycastHit.distance,Space.World); 
    38.                 else
    39.                     transform.position = linePoint;                    
    40.             }
    41.             else
    42.             {
    43.                 transform.position = linePoint;
    44.             }
    45.         }
    46.         else
    47.         {
    48.             transform.position = newPos;   
    49.         }      
    50.     }
    51. }
    52.  
    Please give it a try, test it out, improve it. Perhaps we can communally construct a perfect RigidBody.Move function. I think this is pretty close....
     
    theANMATOR2b likes this.
  5. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Let me highlight two of the current issues.

    The first one is that if you really press it up against a wall and holding it there, it will pop through eventually. Even I put the tolerance at .2f it still does this.

    The other thing is that it sometimes return garbage raycastHit's. I have had to put this in:
    if (rigidBody.SweepTest(sweepDirectionNormalized,out raycastHit,sweepMagnitude+tolerance) raycastHit.distance < sweepMagnitude+tolerance)

    Basically immediately after doing the sweepTest I have to test again to see if the hit point was actually within the specified range. Because under some circumstances it will sweepTest positive on some edge of a planar surface that it really should not be testing positive for, and it will jump across the scene.

    I don't understand why that occurs, my second distance check seem to work well, but it seems weird. Am I doing something wrong or is SweepTest just quirky?
     
  6. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    So after putting in like 30 hours on this I am starting to think it is literally not possible to make a reliable Move function through sweeptest.

    Nothing I can think of can make it 100% and get rid of the glitching.

    It just doesn't work... the SweepTest is not reliable enough.

    There also is no 'rotational sweepTest', so you can't actually test for rotational movement collision.

    Could really use some help on this
     
  7. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Whats wrong with CharacterController?
    Also whats the end goal that you are trying to achieve
     
  8. Smooth-P

    Smooth-P

    Joined:
    Sep 15, 2012
    Posts:
    214
    SweepTest sucks. /end

    There are exactly two things that actually work with any sort of consistency in Unity physics: Raycast and OverlapSphere (and, by extension, CharacterController). Relying on anything else to do "accurate" / "robust" physics will only lead to failure and misery.
     
  9. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Does SphereCast work well?
     
  10. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I am making an app for placing furniture together in spaces. Thus they need to have box colliders, it just wouldn't work with a capsule collider. Making a RigidBody.Move function is an attempt to make a CharacterController.Move function that has a box collider.
     
  11. Smooth-P

    Smooth-P

    Joined:
    Sep 15, 2012
    Posts:
    214
    I haven't personally tried it, but if I remember correctly there's a pretty in depth post by fholm around somewhere that says it doesn't.

    You'll probably have to do a bunch of raycasts, which is apparently all that CharacterController does under the hood in terms of collision detection.
     
  12. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    So is this a Unity thing, or is it a Physx thing? Or is it a 'unity hasnt upgraded to the latest physx' thing?

    Is it possible to plug another collision system into unity?
     
  13. drallim33

    drallim33

    Joined:
    Oct 22, 2013
    Posts:
    7
    Bit of a necro here but I recently discovered what was causing this. Sweeptest seems to hit mesh colliders that it shouldn't, as if the mesh collider was much bigger than it actually is. The RaycastHit then reports the correct distance and hit point though, so there is a discrepancy and you end up having to manually check the distance yourself as you're already doing.

    In the attached picture, the cube on the left has a box collider, and the cube on the right has a mesh collider. The player (center rectangle) has the following code attached:
    Code (csharp):
    1. RaycastHit temphit;
    2.         if(gameObject.rigidbody.SweepTest (Vector3.up + Vector3.rightout temphit, 1f))
    3.         {
    4.             Ray tempray = new Ray (temphit.point, Vector3.down + Vector3.left);
    5.             Debug.DrawLine (temphit.point, tempray.GetPoint(temphit.distance));
    6.         }
    7.        
    8.         if(gameObject.rigidbody.SweepTest (Vector3.up + Vector3.left,   out temphit, 1f))
    9.         {
    10.             Ray tempray = new Ray (temphit.point, Vector3.down + Vector3.right);
    11.             Debug.DrawLine (temphit.point, tempray.GetPoint(temphit.distance));
    12.         }
    View attachment 74106

    So you can see that despite identical code and dimensions, sweeptest returns a hit for the mesh collider from about twice as far away as it does for the box collider.