Search Unity

We need a way to detect and resolve collision hits manually

Discussion in 'Physics' started by PhilSA, Jan 15, 2016.

  1. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    262
    I'd probably need to investigate the options here. The problem occurs because the physics engine postpones the delete operations for a little while until the moment it can be sure everything related to the particular deleted collider was notified properly. Currently this may take 1 frame. Apparently, it's about the code that makes the collisions react to the new contacts 1 frame later than they occur. Adds and removes are the same level operations from that perspective.

    Probably there is a way to hack something inside our cast filter callback. I'll explore more here, but won't promise to deliver anything I'm afraid.
     
    HiddenMonk and Baste like this.
  2. EnokV

    EnokV

    Joined:
    Apr 14, 2016
    Posts:
    49
    Man that looks so sweet. I can almost taste it!
     
  3. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    262
    FWIW, Physics.ComputePenetration and Physics.ClosestPoint (a-la PxGeometryQuery.pointDistance) will be available in 5.6 beta 1, let me know what you think. Thanks for your contribution.
     
    EnokV, HiddenMonk and PhilSA like this.
  4. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    Can't thank you enough. I'm definitely doing something with this the moment it comes out
     
  5. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    Will there also be a ClosestPoints method (multiple contact points)? For example, my current custom capsule collision detection returns a single contact point for primitives, but with mesh colliders it returns multiple points (and it tries to only give the most needed contact points).
    In other words, I want a similar result to what the rigidbody oncollision contact points gives.
     
  6. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    262
    Not this time. I don't think PhysX gets me multiple points.
     
  7. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
  8. EnokV

    EnokV

    Joined:
    Apr 14, 2016
    Posts:
    49
  9. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    667


    Yant for president. Gave it a go right now, will dig deeper when I get home.
     
  10. EnokV

    EnokV

    Joined:
    Apr 14, 2016
    Posts:
    49
    First bug, how exciting! https://puu.sh/sOfgv/189e3eb373.webm

    EDIT: Might add that it's picking up the player collider, that's why it behaves weird - but it only picks it up near the origin which is weird..
     
  11. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    I wana play with it too, but my current character contoller relies on getting multiple contact points for meshes.
    I think the physx findOverlapTriangleMesh will only return the triangles that are hit, not the actual hit points, but that still should be better than nothing since I can use a closestpointtotriangle method on each triangle.
    For example, this is my github page with the ClosestPointOnTriangleToLine method I currently use for capsule/mesh
    https://github.com/HiddenMonk/Unity...erCollision/CCCollision/Collision/Geometry.cs
    Wouldnt be as great on performance as physx, but using the physx findOverlapTriangleMesh for getting the actual triangles id assume would be faster than what I am doing now.
     
  12. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059


    A kinematic rigidbody character controller, with interpolation working, colliding with things exactly how I want to, and reacting to slope changes perfectly
    awwwwwwwwwwww yeaaaaaaa!
     
    Last edited: Dec 13, 2016
  13. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    667
    Yes! YES! Charge my beautiful character controllers your master commands you.



    Having multiple contact points would especially be nice for Physics.SphereCastAll. When it hits a triangle mesh, you only get the first hit. Would definitely be used to get able to get all triangles it hit (I'd use it mainly for ground detection—right now it can be tough to resolve ground beneath the player).

    If you're passing in Collider.center to the function, bear in mind that Collider.center is in local space (so an offset from the local origin).

    EDIT: My poor gif didn't embed :(
     
  14. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    Ground detection is pretty much what I need multiple contacts for. Its also nice for dealing with tough collision situations, but mainly for ground detection.
    I spent a while working on my controller, so going back in trying to make it work with only a single contact is pretty much a no, at least for now.

    Thing is, I dont even know if the physx findOverlapTriangleMesh method would be too useful since I think the performance hit I have at the moment is going through my collided triangles and choosing which ones are best to keep. I kinda just want something like what the oncollisionenter contact points gives.
     
    Last edited: Dec 16, 2016
  15. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    667
    PhysX might do some caching in between frames to have better performance for actually getting the triangles (since it's fair to assume if the colliding controller doesn't move much between frames, it's probably hitting triangles in the local vicinity).

    Either way, being able to get all collided surfaces when querying against meshes would be the holy grail for me (and other people it looks like). We believe in you @yant!
     
  16. EnokV

    EnokV

    Joined:
    Apr 14, 2016
    Posts:
    49
    @Iron-Warrior It was just me being a pleb - I forgot to ignore my own collider as the excitement overwhelmed me. However, I thought it was odd that this issue only happened near the origin(0,0,0).

    Progress:
    https://puu.sh/sRhfG/39b6d7d7d3.webm

    EDIT: Ground clamping in action. It wasn't as straight forward as usually, when you can just apply a downward force but it was simple enough.

    https://puu.sh/sRVK0/3e6edfea83.webm
     
    Last edited: Dec 16, 2016
  17. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    667
    Hey guys, bumping this thread with what appears to be a bug. Normally I'd just submit a report, but since we have an entire thread dedicated to two functions it seemed worth exploring first. ComputePenetration seems to work fine with primitive colliders, but has some issues with mesh colliders. See the gif below.

    http://imgur.com/yMW0pUW

    Each colored object has this script attached.

    Code (csharp):
    1.  
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Pushback : MonoBehaviour
    6. {
    7.     private Collider ourCollider;
    8.     private new Renderer renderer;
    9.     private int mask;
    10.  
    11.     void Awake()
    12.     {
    13.         ourCollider = GetComponent<Collider>();
    14.         renderer = GetComponent<Renderer>();
    15.  
    16.         mask = LayerMask.GetMask("Geo");
    17.     }
    18.  
    19.     void LateUpdate ()
    20.     {
    21.         Collider[] colliders;
    22.  
    23.         if (ourCollider is CapsuleCollider)
    24.         {
    25.             CapsuleCollider capsuleCollider = ourCollider as CapsuleCollider;
    26.  
    27.             Vector3 top = transform.TransformPoint(capsuleCollider.center + new Vector3(0, capsuleCollider.height * 0.5f - capsuleCollider.radius, 0));
    28.             Vector3 bottom = transform.TransformPoint(capsuleCollider.center - new Vector3(0, capsuleCollider.height * 0.5f - capsuleCollider.radius, 0));
    29.  
    30.             colliders = Physics.OverlapCapsule(top, bottom, capsuleCollider.radius, mask);
    31.         }
    32.         else if (ourCollider is SphereCollider)
    33.         {
    34.             SphereCollider sphereCollider = ourCollider as SphereCollider;
    35.  
    36.             colliders = Physics.OverlapSphere(transform.TransformPoint(sphereCollider.center), sphereCollider.radius, mask);
    37.         }
    38.         else if (ourCollider is BoxCollider)
    39.         {
    40.             BoxCollider boxCollider = ourCollider as BoxCollider;
    41.  
    42.             colliders = Physics.OverlapBox(transform.TransformPoint(boxCollider.center), boxCollider.size * 0.5f, transform.rotation, mask);
    43.         }
    44.         else
    45.         {
    46.             colliders = null;
    47.         }
    48.  
    49.         Color color = colliders.Length > 0 ? Color.red : Color.yellow;
    50.  
    51.         renderer.material.color = color;
    52.  
    53.         Depenetrate(colliders, ourCollider);
    54.     }
    55.  
    56.     void Depenetrate(Collider[] colliders, Collider thisCollider)
    57.     {
    58.         foreach (Collider c in colliders)
    59.         {
    60.             Vector3 direction;
    61.             float distance;
    62.  
    63.             if (Physics.ComputePenetration(thisCollider, transform.position, transform.rotation, c, c.transform.position, c.transform.rotation, out direction, out distance))
    64.             {
    65.                 transform.position += direction * distance;
    66.             }
    67.         }
    68.     }
    69. }
    70.  
    So red=overlap is detecting a collision. In the gif you'll see me pause a few times, sink the box in and it will be properly pushed back. But sometimes if I sink the object in deep enough ComputePenetration will return false and no pushback will occur. All of these colliders work fine when intersecting the primitive colliders.

    Has anyone encountered this? It's a bit late for me right now but tomorrow I'll drop a bug report if nobody else has any quick ideas.

    Also—why are we passing in a position and rotation? Colliders are components and are required to have a transform attached are they not? Couldn't it just pull from there?
     
  18. EnokV

    EnokV

    Joined:
    Apr 14, 2016
    Posts:
    49
    It's not mandatory to pass in the current position and rotation of the collider when you expect a penetration, that's probably why you can pass it as an argument, for more flexibility. However, I'm not sure what use case it might have.

    I'm guessing you're testing against a plane in that video? I did a test now and appear to get the same result and I'd assume it's because the plane is infinitely thin.

    Here's a video: https://puu.sh/sZy3w/ef45a83fad.webm

    I'm unsure if this is a bug but either way it should be easy to work around. Just do a spherecast from your feet toward your head if the penetration fails and translate yourself along the y axis by the distance. It shouldn't matter if the terrain is bumpy or not as by then, ComputePenetration should function again.
     
  19. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    667
    This works only if you are certain that the mesh is "below" you. Although my mesh looks like a terrain, it would be any shape and you could be intersecting it at any direction.

    In any case I filed a bug report case #864605.
     
  20. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    Last edited: Mar 8, 2017
  21. Chad-Serio

    Chad-Serio

    Joined:
    Apr 3, 2013
    Posts:
    1
    Physics.ComputePenetration saved me when Collision.impulse would not! Much needed PhysicX internal explosure. Thanks yant
     
  22. Dan2013

    Dan2013

    Joined:
    May 24, 2013
    Posts:
    193
    @Iron-Warrior
    I have the similar feeling, since transform contains position, rotation, and scale.

    @yant
    My understanding is that unequally scaling (scale.x : scale.y : scale.z != 1:1:1) requires a lot of extra work to make ComputePenetration (and ClosestPoint) work correctly. Thus, the Unity guys choose to ignore scaling now?
    Will scaling be dealt with in the future?
    If we want to equally (scale.x : scale.y : scale.z == 1:1:1) scale a collider when using ComputePenetration, what we should do now?
     
  23. Driiades

    Driiades

    Joined:
    Oct 27, 2015
    Posts:
    151
    Hello,
    To not create another thread :

    Physics.ClosestPoint and Physics.ComputePenetration are not sufficient

    I think these two functions are not sufficient for a perfect characterController.
    The first do not provide a point on the collider when the tested point is inside the collider.
    And the second require some kind of collider on the character... which I doesn't want (sry).

    Why not make functions like Physics.OverlapSphere / Physics.OverlapBox for the Physics.ComputePenetration ?

    It's like you made public a Physics.Overlap(collider c) and maybe I'm fool but ... I googlised this and ... this function actually not exist !

    And why not make this consistent ? Physics2D has a Physics2d.Distance that return ColliderDistance2D structure.. ok it's not the same as the Physics.ComputePenetration.

    Really I don't understand why you made this. It's cool, but please, can you make something like the Physics.Overlap functions that doesn't require any collider (We don't want to use physics aspect for our characterController ...so not collider :p ) ? To make code consistent...
     
    Last edited: Apr 10, 2017
  24. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    One thing that's nice about ComputePenetration using colliders is that it allows me to make a character controller that supports ANY shape, even a convex mesh. So I could have a capsule-shaped character, and a flat/horizontal box-shaped character, and both would be managed by the exact same code

    So while I understand the appeal of what you're asking for, we also need a variant of ComputePenetration that stays as it is. Also, if your collider's rigidbody is kinematic and has detectCollisions set to false, then it's as if you had no collider. Or, alternatively, you can have no rigidbody and put the collider on its own "IgnoreEverything" layer

    PS: one thing that needs to be fixed with ComputePenetration is that it can give false results on colliders that are not at a (0,0,0) center and have a non-1 scale
     
    Last edited: Apr 10, 2017
  25. Bezzy

    Bezzy

    Joined:
    Apr 1, 2009
    Posts:
    75
    Thank you everyone on this thread!
    The ComputePenetration is more or less everything I need to do the tightest controller. Now I can be on par with the classic idTech slide move!

    In reply to/support of Driides - yeah, it can feel a little strange to not have primitive variants of the two calls, since everything else does. I'm having to create dummy capsule colliders just to use the functions (I've not had to create colliders up until this point and was quite pleased with myself!).

    Primitive versions of the calls would definitely be nice to have, and feel internally consistent.
     
  26. Driiades

    Driiades

    Joined:
    Oct 27, 2015
    Posts:
    151
    To PhilSa : "you can have no rigidbody and put the collider on its own "IgnoreEverything" layer"

    The IgnoreEverything layer is actually a poor solution that I can't make for my team. (it use 1 layer for.. nothing). And if i have a rigidbody I have to use this to prevent the characterController colliders to not EnterTrigger/ExitTrigger (the characterController do not correspond to the character shape in many many many games).

    Sincerely it's cool for people who want to make it with collider. But we need primitive versions.

    I have the code for the characterController collision detection/depenetration , see this : https://roystanross.wordpress.com/downloads/ ( special thanks to Roystan Ross ^^ )

    I am just waiting for PhysicX performance...
     
    Last edited: Apr 10, 2017
  27. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    The thing I suggested is without rigidbody. You basically just have a CapsuleCollider on a gameObject that is on the IgnoreEverything layer. The only purpose of the CapsuleCollider is to define the shape that will be used by ComputePenetration. I think you can even disable the collider component and it might still work

    And if it' on the IgnoreEverything layer, your characterController2D collider will not detect any collisions with it

    well... it's not for nothing if it has a clear purpose! I don't see why this would become a problem for a team
     
    Last edited: Apr 11, 2017
  28. Driiades

    Driiades

    Joined:
    Oct 27, 2015
    Posts:
    151
    I create generic asset. I can't say to my team : for this to work make a IgnoreEverything layer etc.
    They juste have to put generic asset and .. that work.

    Collider disabled doens't work (it's in the documentation).
     
  29. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    I just checked in my project and disabling the collider does work
     
  30. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,276
    So what is the approach for a single capsule depenetrating from static mesh/heightfield colliders?
    As there is only one point returned I would imagine we can't rely exclusively on this function and will need to still rely on a series of raycasts as backup.
     
  31. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    Not sure I understand your question. What would be a use case for those multiple "collision points"? And how would these points be determined?
    In the meantime, I'm gonna improvise a maybe-answer:

    Let's say you have a CapsuleCollider intersecting a convex mesh collider (static or not), and you use ComputePenetration, it'll give you the optimal direction and distance to depenetrate them.

    If you want to only depenetrate on the Y axis for example, you'd be better off doing something like this:
    • Get the intersected collider via OverlapCapsule
    • Move the capsule's position by (Vector3.up * Capsule.height)
    • CapsuleCastAll downward and filter out all hits that are not from the initial intersected collider (but remember to use the NonAlloc version!)
    • If found a hit, move the capsule's position by (Vector3.down * hit.distance)
    I am almost certain that doing this is much more performant than trying to math your way out of it or trying solve it via several iterations of ComputePenetration (but with the direction projected onto the desired axis). It is also probably much more precise
     
    Last edited: Jun 28, 2017
  32. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,276
    Thanks but I should clarify I already have a bullet proof character controller via ComputePenetration. The issue is that is only useful for one contact per collider. With mesh soups you have multiple in a single frame and should loop through them. We can't with this command, so I thought I'd sound out strategies.

    Your reply is basically what I do - back up raycasts for situations where we are faced with optimised level geo which can't always be a separate box :D
     
  33. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    Are you talking about this sort of deadly situation?


    If so, then yeah, I havent found a solution to that except making my CharacterController solve all collisions through ContinuousCollisionDetection (aka doing iterative CapsuleCasts that get projected along obstruction normals for every movement the character makes). But I haven't tried a whole lot, and I wonder if we could actually make a ComputePenetrationMultiple(Collider[] colliders) by using the original ComputePenetration and just a bit of math.

    After a bit of research and studying how Bullet, PhysX, UE4 and other engines/games made their CharacterControllers, I found out CCD is pretty much the "real" way to make character controllers. And as an added bonus, it makes it "impossible" for your character to move through objects, even if an immense framerate spike occurs

    In the end, I only use ComputePenetration for:
    • When a moving platform moves into my character and must displace it
    • When I want more information about an overlap than just "OverlapCapsule", even if I don't actually depenetrate
    • When I have a character whose collider isn't spherical around its rotation axis, and the character can turn around when next to a wall, and then depenetrate as a result of its rotation
    • For doing an initial depenetration, if the object/character has spawned already intersecting with something else
    • For placing objects in a level (imagine you're in-game and want to place furniture into a room. You can raycast to find walls/floors, place the furniture at that raycasthit point, and then ComputePenetration to depenetrate it)
    • For extra peace of mind, in case something goes wrong with my CCD
     
    Last edited: Jun 28, 2017
    Rog likes this.
  34. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,276
    Yeah the situation of Evil (tm) is fine with 2 separate colliders, but the situation if where it's a single mesh, it's a problem. My approach will be to process the collision geo to solve this rather than trying to make the controller deal with it. Truth is, the controller is only using exposed internals from Physx here, so I believe the right way will be to preprocess meshes if they're used. There's no problem for me for separate colliders per angle/normal :)
     
  35. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    if it's a single mesh, then this could only happen if the mesh is concave, right?

    I guess I kinda avoided that situation entirely by making it a rule that every single collider needs to be convex in my project
     
  36. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    So you mean a situation like this?
    Depenetration.png

    So you are detecting an overlap with 2 colliders, you depenetrate from one, but that depenetration has pushed you more into the second collider. You then depenetrate from the second collider which has pushed you back into the first collider.

    I think the only way to handle that is just an iterative approach, similar to how I handle it with my controller (created before any of the new physics methods mentioned in this thread).
    From memory, I have a loop that detects any overlapping colliders and gathers some data such as closest points, and then within that loop a depenetration method that loops for x amount of iterations to try and depenetrate based on the data calculated from the detected colliders.
    You would most likely not need the depenetration method to be a loop, but I have it as a loop since I do my depenetration based on data calculated from my detection method and in order to help performance, instead of constantly gathering new data each time I try to depenetrate, I instead use the old data and do checks with that data to check if we already depenetrated, though since the data is old, sometimes it thinks we are still penetrating and tries to depenetrate us even though we were already safe. This means I need to just choose a iteration amount that I find works best for most situations.

    How full proof is your ContinuousCollisionDetection approach? I ask because I have tried and tried and failed to use CapsuleCasts for my character controller. I think its impossible to just rely only on CapsuleCasts and get good results due to float point precision errors causing the CapsuleCasts to eventually fail since theyll be starting within the collider we want to collide with.
    You stated you use ComputeDepenetration "For extra peace of mind, in case something goes wrong with my CCD", how often does something go wrong?
    Also, if I use CapsuleCasts, that means I would need to calculate the projection for each collision so that we can slide off walls, but depending on the angles of the colliders (such as in my video above), it will keep projecting off of the colliders for a long time that frame unless if I put in some limit of how many times to project within a frame or something, and it just became a mess from what I remember.
    My new approach is to just move the player, and then iteratively depenetrate. If the player moved more than half their capsule collider radius that frame, we cut their movement up so that each movement is less than their capsule collider radius and run the movement depenetration that way to ensure we dont move past a wall. This means the faster you go, the more iterations, which is bad for performance. This is also not as realistic/accurate and has some flaws/inconsistencies to take into consideration such as going off slopes (of which I avoid by just making it so you go off slopes straight all the time).

    Overall, handling character controller collisions yourself seems to be a big pain and I have for now just settled with what I got, even if its not that great on performance or realism.
     
  37. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    I'd say it is very safe. I must've spent dozens of hours ramming it into complex angled corners, weird shapes, weird complex angled shapes that move rotate and scale, terrain colliders, etc... and it has never ever gone through a collider. (I tested all of this with the ComputePenetration thing turned off, just to be sure)

    There is no doubt about it: making this character controller has been the single most difficult and frustrating thing I have ever done in my entire life. This particular iteration took me about 6 months, but there's also 1-2 years of learning and experimenting behind that.
    ....but it is possible.
    - For corners, I made an algorithm that detects if we are casting against a "blocking corner" based on all the previous iterations made so far, and it stops the iterations accordingly.
    - For floating point errors, I always keep my collider 0.0001 units away from the hit surfaces, along the normals. That way, subsequent casts will never fail.
    - Additionally, all of my casts have a 0.001 "backstep distance", which means they start some tiny distance backwards from their intended startingpoint/direction
    - A small "MinimalVelocityClamp" value insures that there will be no jittering in certain corners
    - etc, etc.......

    The only thing that bothers me right now is that there seems to be a tiny inprecision in terrain collider hits (much like concave collider hits) that becomes noticeable at very slow velocities. I am not actually sure it's the terrain collider's fault, but that's my guess for now.

    I have tried something like that at some point, but I reached a point where I realized I wouldn't be able to manage velocity accurately like this. Unfortunately, I don't remember the details of why that is right now.... I just know I ended up with that conclusion
     
    Last edited: Jun 28, 2017
  38. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    I have tried this, but it fails from what I remember.
    Take this for example.
    Depenetration.png

    In the image above we are seeing a top view of a capsule trying to move between 2 walls.
    The orange arrow is the move direction, and the red arrow is the CapsuleCast hit detection point and normal.
    If we push the capsule in the hit normal by .0001f or whatever amount, there is a chance we are pushed too far into the other wall and will miss any cast detections since we would be starting the cast inside the wall.

    Have you done a test with ComputePenetration off with a test case like this?

    My other approach was to instead of offsetting in the hit normal, I offset in the -moving direction, but that doesnt work depending on your moving angle to the wall, such as the same angle as the left wall and the capsule moving direction in the image. It just sometimes fails due to float point precision.
     
  39. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    Now that you mention it, I don't actually know why my thing works in that situation...

    But my theory is:
    Since I have a "backstep distance" (described in my last post) that is greater than my "collision offset", I will always be able to detect that I'm hitting a surface, even if I'm already penetrating it by [CollisionOffset] amount
     
  40. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    I have also tried starting the cast a little back, but it also failed, for example, imagine those 2 walls in my image in the previous post were actually a single mesh, youd start the cast a little back and might actually start within the wall causing you to miss the other wall since its the same mesh. You probably avoid this since you said you avoid concave meshes, but I still think there are cases where it could fail even with multiple separate colliders.

    However, all these offsets and what not are pretty much what I went through, and it all failed or was just too unreliable. Edge case here, edge case there... was a mess.

    I have just written this SphereController script to try and emulate what I think you are doing.
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(SphereCollider))]
    6. public class TestSphereController : MonoBehaviour
    7. {
    8.     public float mouseSensitivity = 10;
    9.     public float gravity = 9f;
    10.     public float speed = 1f;
    11.     public bool moveEvenIfFailedCollision;
    12.  
    13.     Vector3 velocity;
    14.     int maxCollisionAttemps = 50;
    15.  
    16.     float surfaceOffset = .0001f;
    17.     float backstepOffset = .001f;
    18.     float minVelocityBreak = .001f;
    19.  
    20.     float radius {get {return sphereCollider.radius;}}
    21.  
    22.     SphereCollider sphereCollider;
    23.  
    24.     void Awake()
    25.     {
    26.         sphereCollider = GetComponent<SphereCollider>();
    27.         sphereCollider.enabled = false;
    28.     }
    29.  
    30.     void Update()
    31.     {
    32.         Rotate();
    33.         Move();
    34.     }
    35.  
    36.     void Rotate()
    37.     {
    38.         transform.Rotate(0, Input.GetAxisRaw("Mouse X") * mouseSensitivity, 0);
    39.     }
    40.  
    41.     void Move()
    42.     {
    43.         Vector3 movement = GetMoveDirection() * speed;
    44.         movement += ((Vector3.down * gravity) * Time.deltaTime);
    45.         velocity += movement;
    46.  
    47.         velocity = MoveCollision(velocity * Time.deltaTime);
    48.  
    49.         velocity /= Time.deltaTime;
    50.     }
    51.  
    52.     Vector3 MoveCollision(Vector3 targetVelocity)
    53.     {
    54.         Vector3 originalVelocity = targetVelocity;
    55.         Vector3 origin = transform.position;
    56.  
    57.         int attempts = 0;
    58.         for(attempts = 0; attempts < maxCollisionAttemps; attempts++)
    59.         {
    60.             Vector3 prevOrigin = origin;
    61.             Vector3 hitNormal = Vector3.zero;
    62.             bool hasHit = false;
    63.  
    64.             float castDistance = targetVelocity.magnitude + backstepOffset;
    65.             Vector3 castDirection = targetVelocity.normalized;
    66.             Vector3 castStartBackOffset = origin - (castDirection * backstepOffset);
    67.  
    68.             RaycastHit hitInfo;
    69.             if(Physics.SphereCast(castStartBackOffset, radius, castDirection, out hitInfo, castDistance))
    70.             {
    71.                 origin = CastCenterOnCollision(castStartBackOffset, castDirection, hitInfo.distance);
    72.                 origin += (hitInfo.normal * surfaceOffset);
    73.              
    74.                 hitNormal = hitInfo.normal;
    75.                 hasHit = true;
    76.             }else{
    77.                 origin += targetVelocity;
    78.             }
    79.  
    80.             if(hasHit)
    81.             {
    82.                 //This might not be a accurate conversion, but is fine for testing.
    83.                 //There might be an issue with this since when I move almost parallel against a wall, I seem to slow down a lot as if getting stuck.
    84.                 float remainingDistance = Mathf.Max(0f, targetVelocity.magnitude - Vector3.Distance(prevOrigin, origin));
    85.                 Vector3 remainingVelocity = targetVelocity.normalized * remainingDistance;
    86.                 targetVelocity = Vector3.ProjectOnPlane(remainingVelocity, hitNormal);
    87.  
    88.                 if(targetVelocity.magnitude <= minVelocityBreak) break;
    89.  
    90.             }else{
    91.                 break;
    92.             }
    93.         }
    94.  
    95.         bool failedCollision = attempts >= maxCollisionAttemps;
    96.         if(failedCollision) Debug.LogWarning("Failed collision handling");
    97.  
    98.         if(!moveEvenIfFailedCollision && failedCollision)
    99.         {
    100.             Debug.LogWarning("Aborting movement");
    101.             return Vector3.zero;
    102.         }else{
    103.             transform.position = origin;
    104.             return targetVelocity.normalized * MagnitudeInDirection(targetVelocity, originalVelocity);
    105.         }
    106.     }
    107.  
    108.     Vector3 CastCenterOnCollision(Vector3 origin, Vector3 directionCast, float hitInfoDistance)
    109.     {
    110.         return origin + (directionCast.normalized * hitInfoDistance);
    111.     }
    112.  
    113.     float MagnitudeInDirection(Vector3 vector, Vector3 direction)
    114.     {
    115.         return Vector3.Dot(vector, direction.normalized);
    116.     }
    117.  
    118.     Vector3 GetMoveDirection()
    119.     {
    120.         Vector3 inputDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")).normalized;
    121.         return transform.TransformDirection(inputDirection);
    122.     }
    123. }

    The results are... unreliable.
    Sometimes I start the game and even though I spawn in the air off the floor, I somehow just go through the floor.
    Sometimes I run between two walls and the controller properly detects it cant handle this situation and aborts, and sometimes it just messes up and starts going right through the walls, even if they are separate box colliders and not a concave mesh.

    I just dont see using offsets to try and combat float point errors as being a reliable solution on its own, but you claim all is well, so it really makes me curious since I would have preferred to do things with just CapsuleCasts for performance and simplicity reasons, but after spending months, maybe years, on trying again and again, I just went with a brute force method of penetrate and depenetrate =/
     
  41. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    I just made some more tests, this time with a more extreme case of slightly parallel walls where the angle difference was about 2-3 degrees, and I did manage to go through the colliders this time.

    For now, I guess I'll leave that to my ComputePenetration "safety". (but it'll still torment me forever)

    And yes, that code sample is very similar to what I'm doing
     
    Last edited: Jun 29, 2017
  42. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    =( when will our custom controller collision pains end.
     
  43. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    I could be wrong, but I'm pretty sure character controllers in AAA games are actually just as flawed as ours. It's just that they probably design their levels in a way where there's no slightly parallel wall corridors and triple-plane corners lying everywhere
     
    Last edited: Jun 29, 2017
    roc_unity, Rog and yant like this.
  44. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    Ye, Im sure any physics engine character controller has some kind of flaw or trade off in favor of performance. It still bothers me though T.T
     
  45. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    I would add:

    With the new Physics.Simulate() method, you can now make "frame-perfect" non-kinematic rigidbody controllers that will never go through things. It's something to consider

    EDIT:
    Oh nevermind. PhysX by default seems to have the exact same problems we are facing. Here's a dynamic sphere rigidbody with a constant force applied to it:


    ... which is kinda reassuring, if you ask me. The result looks about the same as what happens when my character CCD fails and the ComputePenetration saves it
     
    Last edited: Jun 29, 2017
  46. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    oooh, didnt know about that (or I think I did but glossed it over).
    However, youd lose control over how the collision is handled. Maybe the OnCollision datas could be useful if they got rid of the garbage collection they caused.
    Also, unless if things have changed, at low speeds even with continuous collision detection enabled on the rigidbody you would penetrate into objects and then be depenetrated, which looks and feels bad. This is apparently due to unity doing some performance optimizations by only using continuous collision detection if the rigidbody is moving fast enough to really need it. I explain it in this old thread https://forum.unity3d.com/threads/r...fter-certain-conditions-faster-better.332085/
     
    PhilSA likes this.
  47. xzbobzx

    xzbobzx

    Joined:
    Apr 7, 2015
    Posts:
    27
    So let's say I did a Physics.ComputePenetration() and I didn't like the results, how should I then go about giving the following collision a custom penetration solving direction? Would it work to just run my own collision calculations and apply it to the rigidbody as a force or somesort?



    The issue occurs when two of my cars hit each other head on at high velocity. My timestep is reduced to 30fps for performance reasons, but it introduces problems with resolving physics.

    Unity thinks it's better to have one car shoot up into the sky while the other passes underneath (3A), while actually (and obviously), you want 3B to happen: both cars bouncing away into the other direction.

    How would I achieve this?
     
    PSInteractive likes this.
  48. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,059
    @HiddenMonk
    Still on the topic of handling slightly parallel corridors with CCD, I think I have found a failure-proof solution.

    In short, at each step of the sweep test iterations (the for loop at line 58 of your example above), whenever you decide to move the character position (or "origin", in your example), you must first make an overlap test at that position. If there is no overlap, just keep moving the position as you would normally. However, if there ARE overlaps:
    • project the movement against all overlap normals (which we find with ComputePenetration)
    • do not move the position. just stay where you were at the start of the iteration
    • proceed to the next step of the sweep iterations, with the new projected movement direction

    This method guarantees that you can never move the character in a position where it's overlapping with something. I'll need more testing, but it seems to work like a charm so far.
     
    Last edited: Jul 6, 2017
  49. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    974
    Ye, I have tried something like that as well and I dont remember having any success (this was before compute penetration came out, so maybe this would work better now?).
    Reason it might fail is due to us not actually depenetrating from the wall, but just attempting to not go inside more, however, due to float point precision issues we might end up very slowly going more and more into the wall.
    Also, saying "project the movement against all overlap normals (which we find with ComputePenetration)" is a lot easier said then done. I think I had a lot of issues handling multiple normals when trying to redirect my velocity and what not.

    For me, it wasnt just a battle of making sure the controller didnt go inside walls, it was also making sure the movement was consistent. When trying to redirect the velocity, I just kept running into issues where if I tried something fancy I would end up getting framerate inconsistent movement. Mixing the CapsuleCast and redirect the velocity method with the brute force Overlap tests and depenetration iterations just wasnt working out for me.
     
  50. EnokV

    EnokV

    Joined:
    Apr 14, 2016
    Posts:
    49
    I think PxMeshQuery::findOverlapTriangleMesh(), PxMeshQuery::getTriangle(), PxMeshOverlapUtil::findOverlap() and the PxTriangle struct would be a great addition to the scripting API and I don't think finding the exact contact point for solid ground detection is all that important. The most important thing is the normal of the triangle, which you get through PxTriangle.

    http://docs.nvidia.com/gameworks/co...pireference/files/classPxMeshOverlapUtil.html

    This scenario is really easy to solve. In a perfect world where your level geometry was separated into ground and walls, this would work perfectly fine. https://puu.sh/wNXE0/c6d51559c4.png this is two convex meshes.

    This scenario is not as easy to solve, but it's not impossible: https://puu.sh/wNXLv/070ccc7c32.png this is a concave mesh. Because this is concave, only one ray is fired in the negative depenetration direction and the result doesn't exactly yield hit info that's easy to work with. I've solved this by always having the bottom of the capsule as a reference and checking the distance between that and the ray hit (the yellow line), this denotes a radius for my feet which is configurable. It handles this situation fairly well and I can also configure how far out on edges you can stand before falling off.

    I also have a backup spherecast to check if there's ground directly below me, however - if you encounter a slope and a wall that is part of the same mesh, yet another check have to be done and this feels like an endless bodge, constantly writing checks for specific scenarios.

    With triangle data, you can iterate over the normals, get angles and perform various checks, separating walls from ground with ease. Atleast in theory it sounds really good, but for all I know this method might be very slow, I have no idea, but Nvidia usually states in their docs if a method is resource intensive or generally slow and I find(in this case) this lack of information a good sign.

    @yant Thoughts on this?
     
    S_Darkwell likes this.