Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice
  2. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  3. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Experimental contacts modification API

Discussion in 'Physics Previews' started by yant, Jul 3, 2020.

  1. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    This sounds like a good fit for my VR shenanigans. Currently I am moving bodies with a PD controller, and if its a scenario where a box is given sufficient force toward a sphere, the box vibrates uncontrollably with random rotations. This sort of thing could be repaired with contact modification?
     
  2. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    That's hard to say... You could definitely modify the collision in interesting ways to try and address it (like capping the impulse), but then you might get anomalies coming out the other end depending on what it does through the physics solver.

    Did you write the PD solver yourself (using AddForce() type stuff on rigidbodies to get physics hands I'm guessing...)?

    I would highly recommend using a configurable joint instead; they can be a pain in the ass to work with, but when used correctly a single configurable joint can be used as a 3D spring to bring a rigidbody to a target location, and also to rotate it to a desired angle. The advantage of using a configurable joint is that it is integrated directly into the physics solver, making the results much more robust (albeit harder to configure).

    A simple approach to doing this is to put the configurable joint on the hand-rigidbody (anchored to the body) and to use the swap-bodies option (this makes it so that the linear spring is not impacted by the rotation of the hand). Like so: configurable hand.png

    Setting the targetPosition and targetRotation can be done in as little as two lines (you might need to set the inverse of the controller rotation and the negative of the position). Like so:


    Code (CSharp):
    1. public void FixedUpdate()
    2. {
    3.     myJoint.targetRotation = Quaternion.Inverse(controllerTracker.localRotation);
    4.     myJoint.targetPosition = -controllerTracker.localPosition;
    5. }
    Physics VR hands is something that I play around with extensively, so I can guarantee this approach works well, but you might need to jack up the solver iteration count if you do not tweak masses and joint settings properly. I do plan to make use of the collision modification API specifically to make the experience more robust and seamless. Custom friction, bouncing, and penetration are what I'm working on right now (ignoring the primary collision while also trying to calculate if a penetrative stab should occur is not easy without it!).

    Here's an example of what you can do with them:

     
    Last edited: Jun 14, 2021
    newlife, JuozasK, lightbug14 and 5 others like this.
  3. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    ROFL... I don't know what kind of game you're making but I'm down to play it!

    Thank you for your tips, I'll give that a go very soon. So the setup to be sure is:

    Hand rigidbody: fully physics (box for now), has a joint on it. The connectedBody is the player's own body?
    VR controller: this is the target rotation / position ?

    Excited to try since even with joints before box to sphere would lead to uncontrollable wobbling due to physics engine pushing it out and it being pulled through it again (I need to make it robust and sliding vs strong penetration targets as the controller passes through things). It seemed to only affect physical box to kinematic or static level sphere though.

    But I wasn't doing what you were, so looking forward to this! thanks again.
     
  4. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Yep! (but it might need to be the negative or inverse of the position/rotation due to using swap bodies and joint placement). You can also apply rotational constraints to the main body-rigidbody in order to prevent the weight of the connected hand tipping it over. Locking rotational constraints on the body instead of making it kinematic allows the hands to influence the body by pushing against other colliders (this is a simple way to implement a knuckle walking controller, and more!). Also, put the hand rigidbody at the pivot of the player body (so that you can treat the center of the player's rigidbody (also the connected anchor location of the hand's joint) as the center of the tracking space). The target rotation and target position you set will be applied relative to the original anchor of the joint (which moves around with the player body), so depending on what you want to achieve, you may need to translate or modify things accordingly. Also, turn preprocessing off (a joint setting)...

    There's also an ultra simple version of things that works like a BB8 robot if you use a sphere collider for the body, and uses no constraints or kinematic settings whatsoever:
    The weight of the head causes the body to rotate, and the joint constantly resolves for the target up relative to the body. A floating hand attached to a freely rolling player body would have the same effect.

    Regarding your specific needs for stability, I also recommend using the temporal gauss seidel joint solver (in physics settings), and setting your solver iterations to around 12. Depending on the strengths you set for the rotation drives in the hand, you should be able to customize exactly how much it can twist due to collision caused torque.
     
    Last edited: Jun 14, 2021
    hippocoder likes this.
  5. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Hey! Thanks for following up. I wasn't able to achieve any stable results or even sane movement with this so far, and I wondered if it's due to how my hierarchy is set up vs yours? could you give me a few more hints on how to set the hierarchy up so this works properly? I've been trying since my last post! I guess it's something silly going on my end.
     
  6. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    There are a bunch of reasons why you might be getting wonky results, but in this case hopefully it's because I was wrong about needing to invert the targets. The code I provided, given that we're using swap bodies, should be this:

    Code (CSharp):
    1.     public void FixedUpdate()
    2.     {
    3.         myJoint.targetRotation = controllerTracker.localRotation;
    4.         myJoint.targetPosition = controllerTracker.localPosition;
    5.     }
    6.  
    (my apologies; i can never keep straight which way is which; configurable joints are like the millenium puzzle of unity components xD)

    Here's an ultra minimalistic package that has a body cube, a hand cube, and a pseudo tracking space to achieve the desired effect (see attached package). regarding hierarchy, there should be none!


    Make the body kinematic do avoid the neat feedback effect shown above! Also, you might want to make the collision detection be continuousSpeculative or dynamic, and you might want to address the damping values on the hand motors to limit the rotation and linear movement speed (the values i put in should make it unrealistically strong and quick)

    Beyond that, there are a metric ton of other settings that might be screwing with your results. Here are some of the obvious culprits:

    1) make sure the joint constraints (and in this case linear spring limit) are properly configured
    2) make sure your joint anchors are in the right places; use the anchor visualizer (the local anchor should be the pivot of the hand, and the connected anchor should be at the center of the connected body)
    3) make sure your masses and inertias are sane (oblong colliders generate bat-S*** inertiaTensor values)
    4) make sure the joint motors are properly configured (else they wont apply any force; they're PD controllers all; the strengths and damping values can be tweaked and limited to give the player hands your desired speed/strength)
    5) make sure temporal gauss seidel is on, and that you have enough solver iterations for 90 FPS joint physics (15/15 are the values i presently like)
    6) if absolutely necessarily, manually set the inertia values of the hand or body in Start() (make inertiaTensor round, and the rotation inertia quaternion the identity). This should only be required in the case of wacky collider shapes.
    7) the origin position (if auto configure connected anchor IS used) of the hand will represent (0,0,0) in the positionTarget's "joint space", so offset this accordingly if you cannot place the hand correctly at it's origin, else configure the connected anchor manually.
    8) the origin rotation of the hand (relative to the connected body) will represent the identity target rotation; keeping starting rotations aligned to world space or the local forward is the best way to avoid dealing with this given this use case.

    Make sure to very carefully examine the settings that I showed in the original image (sneaky things like linear limit spring are easy to miss). The package is quick and dirty, but it should be the most basic starting place to get what you want out of it. Please let me know if you can't figure it out or if there are any issues with the package!
     

    Attached Files:

    Last edited: Jun 16, 2021
    hippocoder and NotaNaN like this.
  7. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Awesome thank you! I gave it a try and could immediately see a lot of promise, and learned a lot from it already. I can see why ConfigurableJoint is such a mystery. Things like swapping body is so counter intuitive and weird. The whole thing is a twisted puzzle of bad UI. Thanks again :)
     
    flimflamm and NotaNaN like this.
  8. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Is this now in beta 2021.2? If so then it is time for me to move to that version of Unity!
     
    adamgolden likes this.
  9. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    595
    Been in since 2021.2.0a12.
     
  10. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Thanks without this I would have had to move to another engine, or do considerable physics custom work, because contact modification really is something I absolutely do need as I'm on performance critical hardware.

    Allows to achieve similar levels of polish that high frequency updates have, with much lower frequency as I can correct issues that crop up with a lower frequency: penetration issues, sliding, erratic responses etc.

    Big win!
     
  11. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    This API might be the ultimate solution to making a fully physics integrated player controller.... Just setting the target velocity of the collision alone deals with friction and creates a jump capbility. Applying rotational contraints to the rigidbody makes it suitable for a physics integrated player body.


    The reason why the sphere is rotating in the opposite direction is due to the force that is being applied to it at the collision point (the sphere rotates opposite the direction due to the torque created by pushing at that point). I am only setting target velocity to get this effect, but it could easily be negated or modified in other ways...

    It sort of *just works*:
     
    Last edited: Jun 26, 2021
    yant, hippocoder and Ruchir like this.
  12. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Does anyone have suggestions for how i can implement some sort of sticky collision system? Anything that is vaguely magnet-like, adhesive-like, or otherwise sticky could be pretty useful to me. I have tried a bunch of stuff but can't seem to get it right, and the physX documentation is scant (they do tease that "sticky contacts" are possible, but they don't give any hints as to how...)...

    I tried things like setting target velocities, but I cant seem to get it right without knowing exactly how all the parameters work... (e.g: is target velocity an acceleration added, or is it a world-space absolute?)

    Inverting normals or setting velocity to a normal (or inverted normal)with an internal collider to keep the objects from fully penetrating very nearly works, but I need it to be even more sticky...
     
    Last edited: Jun 27, 2021
    hippocoder likes this.
  13. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    tractor beams...



    I never managed to make the fingers sticky in a reliable way, but the tinkering gave me ideas for other stuff... The tractor beam, for instance!

    I also decided to apply some motor force regulation tricks to the joint-hand seen above, which is why it could sold a ball fairly easily (more obvious changes will be implemented when I have time, including fancy collision mod stuff!)

    Here's how the tractor beam works

    Code (CSharp):
    1.  
    2.  
    3. for (int i = 0; i < pair.contactCount; i++)
    4. {
    5.    float3 deltaPos = pair.position - pair.otherPosition;
    6.    ContactModType colliderModType = ContactModType.None;
    7.    modifiableColliders.TryGetValue(pair.colliderInstanceID, out colliderModType);
    8.    if (colliderModType == ContactModType.Tractor)
    9.    // we need to know which collider in the pair is the tractorbeam
    10.    {
    11.       // the velocity added to the object
    12.       pair.SetTargetVelocity(i, (Vector3.up + pair.GetNormal(i)) * maxTractorSpeed);
    13.       // apparently this doesn't work....
    14.       pair.SetMaxImpulse(i, pair.GetMaxImpulse(i) / pair.contactCount);
    15.       // altering the normal to negate regular collisions, and to push the object toward the center of the beam, and up
    16.       pair.SetNormal(i, (Vector3.up + new Vector3((deltaPos).x, 0f, (deltaPos).z)).normalized);
    17.       // we dont want friction induced anomalies
    18.       pair.SetDynamicFriction(i, 0f);
    19.       // we want the collision forces to be applied at the center of the collider being tractored
    20.       pair.SetPoint(i, pair.otherPosition);
    21. }
    22.    else
    23.    {
    24.       pair.SetTargetVelocity(i, (Vector3.up + -pair.GetNormal(i)) * maxTractorSpeed);
    25.       pair.SetMaxImpulse(i, pair.GetMaxImpulse(i) / pair.contactCount);
    26.       pair.SetNormal(i, (Vector3.up + new Vector3((-deltaPos).x, 0f, (-deltaPos).z)).normalized);
    27.       pair.SetDynamicFriction(i, 0f);
    28.       pair.SetPoint(i, pair.position);
    29.    }
    30. }
    31.  
    32.  
    EDIT: looks like i borked this a little bit; this actually seems to be using depenetration velocity rather than the speed i defined. I will need to do some rests with separation to see if i can get rid of it....
     
    Last edited: Jun 28, 2021
    JuozasK, hippocoder and Ruchir like this.
  14. Ruchir

    Ruchir

    Joined:
    May 26, 2015
    Posts:
    934
    COuld you upload the image once again, it's missing :)
     
  15. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Here's another link! For some reason the media button is broken

    https://i.imgur.com/hX3U6mk.mp4

    To clarify, there was no image; it was some kind of error due to FireFox I think...
     
    Last edited: Jun 28, 2021
  16. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    I fixed the issue with the tractor beam (it worked, but now it works better!)

    The problem is that depenetration forces were being applied simply due to the normal. Zeroing out the normal breaks the solver by producing infinite forces, but multiplying the normal by infinity seems to negate it (actually i have only tested with larger numbers, but the depenetration gets weaker the higher i factor the normalized normal by).

    Without less depenetration force, the target velocity portion of the script can take more meaningful effect, and the function then has two important parameters: the normal scale factor, which weakens the normal based depentration force, and then the target velocity scale...

    It might be desirable in some cases to keep the normal at a low magnitude in order to elegantly exploit the collision manifold (e.g, for making some sort of black-hole system that requires no fancy maths)



    In this gif you can see balls (or me) that enters the tractor beam being pulled toward the center, in part by the normal depenetration (which is factored up by 10 to make it weak), and also by the light math that calculates a center-and-up direction for the target velocity. Once objects get close enough to the center of the beam, the normal and target velocity up components become strong enough to lift them. Having a modified normal here is especially useful because it is like an additional fast force that pulls objects to the center of the collider. Without it, the target velocity results in too much unbroken momentum gains.
     
    Ruchir, JuozasK and hippocoder like this.
  17. Gustorvo

    Gustorvo

    Joined:
    Oct 26, 2017
    Posts:
    32
    How did you implement the grasping? Would you share the code?
     
  18. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    The basic principle of grasping is just to create a joint between the hand and and an object being collided with, (when the grip button is squeezed). I prefer to use configurable joints but you can use a fixed joint for this fairly easily.

    The fingers themselves are also configurable joints, so it's a full blown physics hand (the only cheats being used are that the player's body is rotationally constrained to prevent falling over and giving an anchor for the hand to push and rotate against, and also the dynamic joint I create between the palm and objects to create a firm grasp). The code for making the fingers flex the way they do is a bit to complex to easily share, but any system that lerps between a min and max target rotation should work. The important part for robust and simple grabbing is to create a joint between the hand rigidbody and the object being picked up.

    But I warn you... This level of physics is nightmarish to prototype and develop, let alone deploy.

    Here's an issue I'm dealing with presently (you cant tell, but I try to swing the board like a sword when the fingers and hand bug out):



    The cause is, somehow, collisions, (and not due to contact modifications either)...

    A firm configurable joint is created between the palm and objects being picked up, like the long 2x4 board I pick up at the end of the video. The fingers apply pressure of their own and can manipulate balls or help my climb objects, but they're not strong enough to pick up a heavy board and swing it around like that; the thing moving the board is almost entirely the joint linking it to the palm rigidbody). The edge case I'm dealing with is when I grab heavy objects from oblong places with strange leverage and try to swing it quickly: the finger colliders up against the past moving and high-inertia'd board decide they're going to start depenetrating, and mutual error feedback causes them to descend into joint explosions. (At least this is what I suspect is happening according to my tests, aside from one or two other buggy anomalies I have identified....).

    I'm probably going to try using contact modification to address this, but I'm not sure how yet.

    I need to understand how I can address different colliders in a collision pair separately through the contact mod system (for example, I would like to have two colliding objects be given equal and opposite target velocities through SetTargetVelocity(), but any velocity passed into this field seems to just apply the same target world-space velocity for both colliders in a given pair.

    Another example I don't understand is how the normal is supposed to represent both colliders. I have assumed that the primary collider in a pair (the non "other" one) is the owner of the normal returned by GetNormal(), and that the "other" one uses the same normal, but inverted. Is this accurate? I have also been assuming that I need to perform a check to make sure any relative math is done correctly.

    Sorry for the large post and the seemingly basic questions, but I have searched extensively for specific documentation about the contact mod system from Physx and elsewhere, and the details they give about collisions don't address the low level solver math of contact mod parameters...
     
  19. Gustorvo

    Gustorvo

    Joined:
    Oct 26, 2017
    Posts:
    32
    Thanks for sharing your setup and workflow... I'm new to contacts modification API and am afraid I can't answer your questions, however it looks like your colliders depenetrating each other extensively. Try to lower maxDepenetrationVelocity of your rigid bodies. I had the similar issue with articulation bodies.
     
    flimflamm likes this.
  20. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    This is a good idea but unfortunately it won't fix the specific issue I'm seeing. It might be a part of the solution though...

    The fingers actually work splendidly when I'm not using dynamic joints for gripping, and instead just relying on finger joints and friction....

    Except now I'm dealing with a new issue:



    The tractor beam effect breaks when I actually deploy it on a Quest build... It works in the editor, and the locomotion contact mod works fine on the quest, but most colliders bounce right off of it... Very confusing because the fingers still partially work with it...

    Here's the tractor function I'm using.... it's pretty untweaked but works fine in editor...

    Code (CSharp):
    1.     private void ModifyTractorPair(ModifiableContactPair pair)
    2.     {
    3.         for (int i = 0; i < pair.contactCount; i++)
    4.         {
    5.             // caching some snizz for this pair
    6.             float3 deltaPos = pair.position - pair.otherPosition;
    7.             ContactModType colliderModType;
    8.             modifiableColliders.TryGetValue(pair.colliderInstanceID, out colliderModType);
    9.             if (colliderModType == ContactModType.Tractor)
    10.             // we need to know which collider in the pair is the tractorbeam
    11.             {
    12.                 // the velocity added to the object
    13.                 pair.SetTargetVelocity(i, (Vector3.up + pair.GetNormal(i)) * maxTractorSpeed);
    14.                 // apparently this doesn't work....
    15.                 pair.SetMaxImpulse(i, pair.GetMaxImpulse(i) / pair.contactCount);
    16.                 // altering the normal to negate normal surface collisions, and to push the object toward the center of the beam, and up...
    17.                 pair.SetNormal(i, (Vector3.up + new Vector3((deltaPos).x, 0f, (deltaPos).z)).normalized * 10f);
    18.                 // we dont want friction induced anomalies
    19.                 pair.SetDynamicFriction(i, 0f);
    20.                 // we want the collision forces to be applied at the center of the collider being tractored
    21.                 pair.SetPoint(i, pair.otherPosition);
    22.             }
    23.             else
    24.             {
    25.                 pair.SetTargetVelocity(i, (Vector3.up + -pair.GetNormal(i)) * maxTractorSpeed);
    26.                 pair.SetMaxImpulse(i, pair.GetMaxImpulse(i) / pair.contactCount);
    27.                 pair.SetNormal(i, (Vector3.up + new Vector3((-deltaPos).x, 0f, (-deltaPos).z)).normalized * 10f);
    28.                 pair.SetDynamicFriction(i, 0f);
    29.                 pair.SetPoint(i, pair.position);
    30.             }
    31.         }
    32.     }
    UPDATE: the issue is not specific to builds for the quest; it seems to happen for PC builds too.... Going to test some hypothesis to fix this...

    UPDATE 2: Tried everything I could think of to address the issue. Given that it exists on both android and windows builds, I'm guessing it's simply a bug that does not manifest in the editor. Error logs from the windows build are dry as a bone... I'll have to do further testing when I get a chance

    UPDATE 3: I managed to track down the source and nature of the bug: In the editor play-mode, the primary collider being returned in the pair is NOT the modifiable one, but in an actual build, it will tend to be the one being reported. Beyond this, there might be some strange inverting going on, but I need to do more tests before I figure out exactly what is being borked...
     
    Last edited: Jul 4, 2021
    NotaNaN and hippocoder like this.
  21. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Hi Yant! I'm about 95% sure I've identified a bug(s) with the contact mod API (unity version 2021.2.0a.17.2114)

    When in editor-play-mode, the "other" (otherColliderInstanceID)collider being reported in a given pair will be the modifiable one, and in compiled builds it will be the primary collider (colliderInstanceID), or at least this is true for my environment.

    In addition to this, the results of SetTargetVelocity() are inconsistent; any worldspace velocity passed to SetTargetVelocity() will be inverted in deployed builds (AFAIK it is correct in editor mode, and inverted in deployed builds). This is likely due to an issue with which collider is being reported as the primary one, but it might be unrelated.

    I was being really confused by the confluence of both of these issues at once (my attempts to understand one aspect of the problem was always confounded by the other!). Once I figured out that deployed builds were reporting the opposite collider as modifiable, I was able to narrow down the real issue to SetTargetVelocity(). Here's an example of how unintuitive the issue was: (this was the code required to get identical behavior)

    Code (CSharp):
    1.             if (modifiable == this)
    2.             {
    3.                 pair.SetTargetVelocity(i, (-Vector3.up + -pair.GetNormal(i)) * maxTractorSpeed);
    4.             }
    5.             else if (modifiable == other)
    6.             {
    7.                 pair.SetTargetVelocity(i, (Vector3.up + -pair.GetNormal(i)) * maxTractorSpeed);
    8.             }
    9. }
    The up vector needs to be inverted, but the normal does not, because it (correctly) inverts itself depending on which collider is the main collider of the pair, and I'm trying to derive a consistent direction based on the object entering the "tractor" collider. I can solve the issue by manually inverting some code but do let me know if and when a fix is implemented!.
     
    Last edited: Jul 4, 2021
    NotaNaN, PandaArcade and hippocoder like this.
  22. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    +1 just diving into this now, and appreciate you sharing findings.
     
    flimflamm likes this.
  23. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Highly suggest you give it a whirl :D

    I will not, so feel free.
     
  24. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    My pleasure! I plan to keep posting what I can figure out. Right now I'm looking for a good way to negate the hard depenetration constraints from within the contact mod pair callback, and I also want to investigate
    setMaxImpulse() to see if I can get it working, because until now it has had no effect unless zeroed out (it might be working, but only for the soft constraint portion of the solver, which are the non depenetration forces AFAIK). Let me know if you figure anything out!
     
    Last edited: Jul 4, 2021
    hippocoder likes this.
  25. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Experimenting with a balance between an outward pushing velocity (from a center point) and an inverted and scaled normal (which makes the collider *suck* instead of repel, and more slowly due to the scaling (note: the scaling breaks around 1000 for a mass of 1; it creates forces that are too large, somehow...)



    Code (CSharp):
    1.     private void ModifyGravityPair(ModifiableContactPair pair)
    2.     {
    3.         for (int i = 0; i < pair.contactCount; i++)
    4.         {
    5.  
    6.             ContactModType colliderModType;
    7.             modifiableColliders.TryGetValue(pair.colliderInstanceID, out colliderModType);
    8.             if (colliderModType == ContactModType.Gravity)
    9.             {
    10.                 pair.SetTargetVelocity(i, -pair.GetNormal(i) * velocityFactor + velocity);
    11.                 pair.SetNormal(i, pair.GetNormal(i) * normalFactor);
    12.                 pair.SetPoint(i, pair.otherPosition);
    13.             }
    14.             else
    15.             {
    16.                 pair.SetTargetVelocity(i, -pair.GetNormal(i) * velocityFactor - velocity) ;
    17.                 pair.SetNormal(i, pair.GetNormal(i) * normalFactor);
    18.                 pair.SetPoint(i, pair.position);
    19.  
    20.             }
    21.         }
    22.     }
    The extra + and - velocity is being used to negate gravity.
     
  26. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Figured out the trick to getting slow orbiting. It's essentially the same as the above, except gravity must be turned off for an orbiting rigidbody (I cant correctly negate it with SetTargetVelocity() TT), separation must be zeroed out, and target velocity must be set to zero.
    (slow orbiting at the end). I also zeroed out friction and drag in order to keep them orbiting forever....



    Code (CSharp):
    1.         for (int i = 0; i < pair.contactCount; i++)
    2.         {
    3.  
    4.             ContactModType colliderModType;
    5.             modifiableColliders.TryGetValue(pair.colliderInstanceID, out colliderModType);
    6.             if (colliderModType == ContactModType.Gravity) // if the primary collider is the "gravity well"
    7.             {
    8.                 pair.SetTargetVelocity(i, Vector3.zero);
    9.                 pair.SetNormal(i, -pair.GetNormal(i));
    10.                 pair.SetPoint(i, pair.otherPosition);
    11.                 pair.SetSeparation(i, 0f);
    12.                 pair.SetDynamicFriction(i, 0f);
    13.             }
    14.             else
    15.             {
    16.                 pair.SetTargetVelocity(i, Vector3.zero);
    17.                 pair.SetNormal(i, -pair.GetNormal(i));
    18.                 pair.SetPoint(i, pair.position);
    19.                 pair.SetSeparation(i, 0f);
    20.                 pair.SetDynamicFriction(i, 0f);
    21.  
    22.             }
    23.         }
     
    hippocoder likes this.
  27. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Just some cool on-going experiments:



    The goal here is a hollow collider that sucks in and traps other objects for catching fast moving and heavy objects in VR, and achieving it is quite easy:

    Code (CSharp):
    1.     private void ModifyInvertPair(ModifiableContactPair pair)
    2.     {
    3.  
    4.         for (int i = 0; i < pair.contactCount; i++)
    5.         {
    6.             if (pair.GetSeparation(i) < -0.1f)
    7.             {
    8.                   pair.SetSeparation(i, -.2f - pair.GetSeparation(i));
    9.             }
    10.  
    11.  
    12.             pair.SetNormal(i, -pair.GetNormal(i));
    13.  
    14.             pair.SetPoint(i, pair.otherPosition);
    15.             pair.SetDynamicFriction(i, friction);
    16.         }
    17.     }
    Here's what's happening: the normal of the collision is always inverted, making the collider into a kind of black-hole that just sucks everything chaotically toward the middle. However, when the object gets deeper into penetration than 0.1f meters, the pair.SetSeparation() function starts bringint it back toward 0f, until at -0.2f meters of penetration, the separation will be set as 0 or higher. This creates a one way collider that sucks from the outside, and has an internal collision surface that is offset (toward the interior) from the outside of the collider. The purpose of the offset is to give the internal collision surface some depth/thickness, and im setting the point to the center of the collider being sucked.

    Ontop of this, I also have a spherical trigger that turns gravity off (and applies some drag) for objects that are inside, just to give it a more interesting gravity-modification aesthetic. There's also some layer collision matric stuff used to prevent the inverted collider from sucking itself (and my VR hand) into the ground and other statics).

    Next, I'm going to try making rope!
     
    Last edited: Jul 5, 2021
  28. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43


    It's not pretty, but it sorta proves it in principle. It uses contact modification to make the capsules hollow for the green berings only.

    Once I get a better understanding of how to anticipate and modify collision points (and therefore separation), I should be able to do this without berings to make it more stable, but it's a bit too complex for now. I'm pretty sure it's doable though...
     
  29. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    @yant
    Is it a broken thing or is inversion of direction something we have to handle ourselves? Just wondering if it's actually broken.
     
    PandaArcade likes this.
  30. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    I used to be confident that it was a bug because I expected the targetVelocity to define a world-space direction that would apply to both colliders (as if to define a moving collision point in the case of two non-static colliders), but it could also be the case that it's supposed to try and send the relative collider points in equal and opposite directions, using the primary collider as the positive reference ).

    As it stands, (2021.2.0a17.2411), target velocity appears to apply an equal and opposite force. It's essentially pushing the colliders away from each-other (at the collision point) using target velocity like a normal. This is actually sort of desirable because it makes targetVelocity a bit easier to use in certain cases...

    For example, now that I know the target velocity forces are equal and opposite, I can keep using it for locomotion without worrying about an infinite movement cheat by using a held non-ground object (i can essentially let my feet try and locomote against anything and it should work as intended). Here's me locomoting over some boards that previously would have stalled the player-controller


    (This kind of locomotion is really quite good... It's especially good for VR because physics makes motion feel more realistic, creates predictability; predictability ends up giving the player more control and freedom, which lets them feel more embodied).
     
    Last edited: Jul 15, 2021
    hippocoder likes this.
  31. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Just not ever getting any values from GetTargetVelocity. Anyone else having issues?
     
  32. lightbug14

    lightbug14

    Joined:
    Feb 3, 2018
    Posts:
    447
    In my case, target velocity is always zero no matter what. I think this is working as expected since this is a "target" velocity, not the actual contact velocity (which is probably defined by the solver later). Inside the event loop you can define the target velocity that you want, however, that's not necessarily the velocity that you will get (friction also plays a big role).

    If this is correct (big if), then i don't understand why GetTargetVelocity exist. Maybe if you want to read that value inside the loop (?).
     
  33. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Getting weird results from pair.GetNormal.

    The contact modification is on my character controller - a capsule collider and rigidbody.
    The offending object is a box collider rotated 45 degrees.

    There is also a flat floor box without any rigidbody.

    When the box collider is static I get problems like normals scaled to infinity with point originating at 0,0,0.
    When the box is kinematic with rigidbody these problems seem to go away.

    This happens when I am in contact with floor and rotated box collider. Debugging the angles gives me NAN or 90 degrees or 180 - in short I think there's the possibility of odd results unless a kinematic rigidbody is added to the static colliders.

    Posting here to see if any findings from others.
     
  34. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    As far as I can gather, the target velocity parameter is intended only for niche contact modifications, and isn't a standard part of the collision solver.

    The Get function exists simply to check what you have already set. It's basically to check the target velocity that you might have already set in a different collision point for the same modifiable collision pair.

    You might be seeing instances where a collision is generated due to contact offset stuff, but where the colliders aren't actually touching (separation above 0). I'm not positive, but if you check for separation <= 0f, then you should get more reliable readings.

    I have also heard murmurings about issues with static colliders that aren't given kinematic rigidbodies. It's probably best to put all your static geometry under the same object, and give them all one kinematic RB.
     
  35. Gustorvo

    Gustorvo

    Joined:
    Oct 26, 2017
    Posts:
    32
    Are there any plans for implementing friction modification. As fas as I'm concerned, we're not able to modify it, are we?
     
  36. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Friction is working fine in my tests. Currently having contaminated results though from other actors also using this code so I best look at what's going on with threaded stuff.

    Huh, it looks like this callback is populated with stuff from anything goes and not specific in any way so have to filter it:~


    Code (CSharp):
    1.             int pairLength = contactPairs.Length;
    2.             for (int i = 0; i < pairLength; ++i)
    3.             {
    4.                 ModifiableContactPair pair = contactPairs[i];
    5.                 if (pair.colliderInstanceID != id)
    6.                 {
    7.                     Debug.Log("rejected "+i);
    8.                     continue;
    9.                 }
    10.  
    11.     ... valid for this id
    12.  
    Seems weird to me that ALL things with modifiable contacts have to process and reject ALL other things too, but maybe that's normal? can someone clarify?
     
    Gustorvo likes this.
  37. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    595
    Hey @flimflamm & @hippocoder, sorry for the silence on my end, been having a break. May I ask you to open tickets for the misbehaviours observed? That helps transparency and tracking. Thank you so much for your help. <3
     
    NotaNaN, hippocoder and adamgolden like this.
  38. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Hello Yant! Hope you had a nice break. I have been hesistant to do so because I feel It was a case of my error, not yours. I have rejigged my code so that there are two for loops, one for pairs, and another for contacts and I think that was the problem! I was using pair index for contact.

    If it pops up again with this new code, I'll let you know. Take care (and thanks!)
     
    NotaNaN and yant like this.
  39. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    What would be a GREAT sample for the docs is an example that reproduces the behaviour of OnCollisionStay - as I think most people will want to switch to this new callback for control and performance in their games (once they know about it!)

    It's a little tricky to do as you also have to ignore contacts moving away from a surface and so on (minor filtering) I think? I used a dot product for velocity and normal. Would love to see how OnStay does it actually.

    Thanks again!
     
  40. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    One of the conundrums I'm facing is relative velocity; in order to calculate it inside the callback, we require the linear and rotational velocities of both rigidbodies. This means we need to cache every single RB velocity in the scene (by instance ID so it can be accessed via hash map in the callback). If we only start caching velocities of non-contact-modifiable rigidbodies once they start colliding with a modifiable collider, then we're still going to miss an accurate relative velocity calculation on the first tick of the collision. It's a bit of a conundrum.

    And don't get me started on trying to calculate the relative velocity of colliders *at a given point*, which is what I really need!

    Will do. The two bugs I have identified are A) the mismatch between which collider comes first in a given pair in editor vs built environment, and B) SetMaxImpulse does not work properly (it is only good for completely zeroing out impulse, but doesn't appear to limit anything otherwise).

    EDIT: where should I file the bug report? Through unity, or on Git?
     
    yant and NotaNaN like this.
  41. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    595
    I'm really interested to see a project illustrating this ^. We're using the same PhysX configuration, and pretty much the same physics integration module really both in Editor and in Player. Some interesting shenanigans are at play. If you end up opening a ticket, do post the ID here too so that I can put it on a fast track.
     
    hippocoder likes this.
  42. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    595
    Ouch. ModifiableContactPair has pointers to both PxActors, and it's actually easy to get both velocities going from here. I believe both getters are reentrant in PhysX, so there should be no threading problems here. I could make a custom build exposing those for you for testing purposes in a matter of a few days, but the problem is that I'm not 100% sure I could make the change into 2021.2 unfortunately... (Being in beta means no new APIs are added in general). What do you think, do you want to test it still? Worst case we put it in 2022.1.
     
  43. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    595
    The built-in Unity Bug Reporter, right from the Editor itself. Thanks!
     
  44. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Submitted a bug report via editor for the collider mis-match bug!

    I managed to figure out the exact circumstances that reproduces the collider mis-match error (it happens when both colliders have a rigidbody, and one or more is modifiable). Without a rigidbody on the non-modifiable collider, the problem doesn't occur.

    I included a small scene that shows the problem on play, and on build-and-run, but it should be very easy to reproduce.

    Should I also submit a report for SetMaxImpulse() not working?

    I would definitely use it. I'm on the absolute bleeding edge of Unity versions right now (2021.2.0b5), and at present I have a lot of potential use cases for it!

    EDIT: Case numbers:

    Collider mis-match bug: Case 1356219
    SetMaxImpulse() bug: Case 1356258
     
    Last edited: Aug 7, 2021
    yant likes this.
  45. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    595
    Hey @flimflamm

    As discussed above, please find the build with extra getters for linear and angular velocity over here: https://beta.unity3d.com/download/5fa526b8e611/public_download.html.

    This is based on a fresh 2021.2 build that hasn't yet been released I believe -- so please be aware of the regular precautions such as backing up your project etc.

    As with many preview builds, It's just an Editor build, and doesn't have any players there, but I do hope it's still enough for testing purposes. I'm not directly sure any docs are available with it implicitly, so here is a quick lump of new methods that I exposed for you:

    Code (CSharp):
    1. public struct ModifiableContactPair {
    2.         public Vector3 bodyVelocity
    3.         public Vector3 bodyAngularVelocity
    4.         public Vector3 otherBodyVelocity
    5.         public Vector3 otherBodyAngularVelocity
    6. }
    Please let me know what you think. :)

    Anthony
     
    NotaNaN, flimflamm and hippocoder like this.
  46. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Tested this, and it works! Using this I finally managed to figure out the all important relative velocity (including angularVelocity) math. I have not fully debugged it yet, or gotten the directional friction tweaked properly for a snake, but it's a veritable start!



    Stabbing physics is up next, but I also have drag-sounds, hit sounds (and accompanying effects), sticky contacts, and more to implement using these!

    Is there any chance that these could be exposed in an upcoming 2021.2 build, or perhaps a custom build with players (any recent 2021.2 build would suffice)? It would be a massive benefit! Android (for Quest VR) is my main build target (with PCVR being a secondary target).
     
    Last edited: Aug 13, 2021
    hippocoder likes this.
  47. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Managed to get directional friction more or less perfected, but surprisingly I ended up using target velocity as a way to negate or apply friction on a given axis, instead of actually setting friction.

    Code (CSharp):
    1. Matrix4x4 localSpace = Matrix4x4.Rotate(pair.rotation);
    2. Vector3 velocity = localSpace.inverse.MultiplyVector(GetCumulativeRelativeVelocity(pair, i, 0)); // to local space
    3. velocity.z *= 0.4f;
    4. velocity.x *= -1f;
    5. velocity.y *= -1f;
    6. velocity = localSpace.MultiplyVector(velocity); // to world space
    7. pair.SetTargetVelocity(i, velocity);
    It took me a fair bit of thinking, but I finally realized that for directional friction to really work like I want it to, the forces of friction needed to be axis dependant (instead of just using a scalar by setting friction value). Since target velocity is itself mitigated by friction (i believe it actually works by manipulating the same force vector that friction generates/uses) this seems like the perfect approach. In the above code example, inverting the relative x and by velocity of the rigidbody, and then setting that as the targetVelocity of the collision,will cause available friction force in those given axis to work against the direction of motion. Multiplying the z velocity by 0.4 is a way of reducing the friction for that axis (setting it to 1 would cause it to have no friction in that axis). "GetCumulativeRelativeVelocity(pair, i, 0)" is just a method that derives relative velocity of the colliding bodies (including the point displacement resulting from the rotation of rigidbodies).

    The results look absolutely perfect: https://i.imgur.com/WsyjE07.mp4
     
  48. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    I also need on collision events from the contact mod API... This is what I have gotten working so far:

    Code (CSharp):
    1.  
    2. private NativeArray<int> collisionEventSubscribers;
    3. private NativeHashMap<int2, bool> collidingRigidbodyPairs;
    4.  
    5. public void MainCallBack(PhysicsScene scene, NativeArray<ModifiableContactPair> contactPairs)
    6.     {
    7.         foreach (var pair in contactPairs)
    8.         {
    9.             if (collisionEventSubscribers.Contains(pair.bodyInstanceID) || collisionEventSubscribers.Contains(pair.otherBodyInstanceID))
    10.             {
    11.                 DetectCollisionEventPair(pair);
    12.             }
    13.     }
    14.  
    15. private void DetectCollisionEventPair(ModifiableContactPair pair)
    16.     {
    17.  
    18.         if (NativeArrayExtensions.Contains(collisionEventSubscribers, pair.bodyInstanceID)) {
    19.  
    20.             int2 pairKey = new int2(pair.bodyInstanceID, pair.otherBodyInstanceID);
    21.  
    22.             if (collidingRigidbodyPairs.ContainsKey(pairKey))
    23.             {
    24.                 if (pair.GetSeparation(0) > 0.01f)
    25.                 {
    26.                     Debug.Log("collision EXIT");
    27.                     collidingRigidbodyPairs.Remove(pairKey);
    28.                 }
    29.                 else
    30.                 {
    31.                     Debug.Log("collision STAY)");
    32.                 }
    33.             }
    34.             else if (pair.GetSeparation(0) <= 0.1f)
    35.             {
    36.                 Debug.Log("collision ENTER");
    37.                 collidingRigidbodyPairs.Add(pairKey, true);
    38.             }
    39.         }
    40.         if (NativeArrayExtensions.Contains(collisionEventSubscribers, pair.otherBodyInstanceID))
    41.         {    
    42.             int2 pairKey = new int2(pair.otherBodyInstanceID, pair.bodyInstanceID);
    43.             if (collidingRigidbodyPairs.ContainsKey(pairKey))
    44.             {
    45.                 if (pair.GetSeparation(0) > 0.1f)
    46.                 {
    47.                     Debug.Log("collision EXIT");
    48.                     collidingRigidbodyPairs.Remove(pairKey);
    49.                 }
    50.                 else
    51.                 {
    52.                      Debug.Log("collision STAY)");
    53.                 }
    54.             }
    55.             else if (pair.GetSeparation(0) <= 0.1f)
    56.             {
    57.                 Debug.Log("collision ENTER");
    58.                 collidingRigidbodyPairs.Add(pairKey, true);
    59.             }
    60.         }
    61.     }
    62. }
    The basic way it works is by tracking currently colliding pairs by storing them as the key in an int2,bool hashmap (the first int is the subscribed rigidbody instanceID, and the second is the rigidbody instanceID it is colliding with). If a collision-event-subscribed rigidbody and its pair make an int2 key that does not exist in the "collidingRigidbodyPairs" hashmap (and the separation is under a given threshold), that means it is a collisionEnter(). If the key already exists, then that means it's a collisionStay(). If the separation is greater than 0.1f, then that triggers a collisionExit() and removes the pair from the "collidingRigidbodyPairs" hashmap.

    The code is doubled up like this because the same callback will take take of the events for both colliding rigidbodies, assuming they are both subscribed in the collision event array. It seems to work, but the only caveat is i dont yet know how to deal with sleeping colliders, which should still throw an onCollisionStay (I think). This is what I might use the "bool" value for in the int2,bool hashmap...

    If you have a simpler or bug free approach, or if you can improve upon this one, please help me out!

    edit: I should note that this wont reproduce the exact behavior of collision events; the on collision enter, stay, and exit events normally are not specific to individual rigidbody pairs, but instead depend on the given body and any/all other colliders it is interacting with.
     
    Last edited: Aug 23, 2021
    NotaNaN and hippocoder like this.
  49. flimflamm

    flimflamm

    Joined:
    Jan 6, 2020
    Posts:
    43
    Here's an updated version that works like the standard mono versions: https://pastebin.com/6Q6XSzHL

    This one should reproduce the standard collision event behaviour. Instead of tracking specific colliding pairs, it just tracks whether a given body is colliding with any other body, and whether it was also colliding last tick. It also correctly handles cases where a collision pair de-collides faster than the collision offset can account for (simply checking for separation greater than zero is not always enough).

    Re-using collision callbacks (the physics setting) also seems to affect some on the code here, not quite sure why yet.

    I'm also not entirely sure why I need the Detect CollisionExits in fixedUpdate rather than at the end of the callback; seems like the list/dicts are not updated until the callback is finished.
     
    Last edited: Aug 31, 2021
    romanpapush, hippocoder and NotaNaN like this.
  50. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Awesome work. I think Unity would do well to add a similar example to the docs. I'm also hoping that Unity will consider an optimisation to remove redundant stuff so this can fully replace + be faster.