Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Wheel Friction Curve Revealed

Discussion in 'Editor & General Support' started by neilt, Oct 2, 2012.

  1. neilt

    neilt

    Joined:
    Aug 25, 2012
    Posts:
    1
    The Wheel Friction Curve used by Unity has major problems.

    Most importantly there are significant differences between the actual curves and the documentation! In particular, the extremumValue setting is a curve gradient (i.e. tangent) at extremumSlip not, as stated in the documentation, a peak value of the curve. Similarly asymptoteValue is a gradient or tangent at asymptoteSlip, not (as documented) a value on the curve.

    I've written a C# script to measure the Wheel Friction Curve and write the results to a csv file which can then be plotted using Excel or Gnuplot. (Gnuplot is an easier option, and a Gnuplot script is appended in a C# comment.)

    Here is an example of an actual curve (with settings that are "workable", considering the obvious problems). PhysX/Unity values for this curve are:
    extremumSlip = 0.6
    extremumValue = 1.2
    asymptoteSlip = 2.0
    asymptoteValue = 0.2
    stiffness = 760
    As you can see the curve is not at all what you'd expect.
    $wfc-0.60-1.20-2.00-0.20-760-6.gif
    Far worse is what you get if you select 'reasonable' values based on following the documentation (see below). PhysX/Unity values for this curve are:
    extremumSlip = 1.0
    extremumValue = 1.0
    asymptoteSlip = 2.0
    asymptoteValue = 0.8
    stiffness = 760
    What you are expecting is something like the purple curve, what you get is the red curve!! Friction increases enormously with side slipping and the car will probably trip. (That's what alerted me to the problem - once the car begins slipping it shouldn't trip.)
    $wfc-1.00-1.00-2.00-0.80-760-6.gif
    Notes on the Graphs
    • I've added the prefix Physx (e.g. PhysxExtremumValue) on all the variable names to indicate these have meanings under the present situation (Unity v3.5.6) which don't match the descriptions in the documentation.
    • The green and blue lines are drawn as guides:
      • The green line has a gradient matching the tangent at (PhysxExtremumSlip, PhysxExtremumValue).
      • The blue line has a gradient matching the tangent at (PhysxAsymptoteSlip, PhysxAsymptoteValue). This explains why cars trip at high slip speeds; friction keeps on increasing with slip, when instead the Asymptote should have been horizontal.
    • I've experimented a lot and as far as I can tell, the "crazy" behaviour for slip < 0.5 m/s is always present and is not an artefact of my test code.
    • With the current implementation of the Wheel Friction Curve the most important value is PhysxAsymptoteValue. Too high and your car will trip at high slip speeds!
    • For more discussion see the opening comments in the code.

    My thoughts on next steps are:
    1. Please test my code and confirm that I haven't made any mistakes!
    2. Report this as a bug. Maybe Unity can lobby NVIDIA to make a fix! (My understanding is that Unity just calls the PhysX/NVIDIA Wheel Friction Curve code.)

    Your thoughts and comments are welcome.

    regards,
    Neil
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5. using System.IO;
    6.  
    7. /*
    8. This code enables the plotting of the Wheel Friction Curve.  See http://docs.unity3d.com/Documentation/ScriptReference/WheelFrictionCurve.html
    9. Please try a few plots and compare your findings to the following:
    10. 1. The implementation of the friction curve doesn't match the PhysX/NVIDIA/Unity documentation!
    11.    [Actually, the design intent for the friction curves given in the documentation seems OK,
    12.    BUT the implementation is incorrect!]
    13.    In particular ExtremumValue setting is a curve gradient (i.e. tangent) at ExtremumSlip NOT, as stated in the
    14.    documentation, a peak value of the curve.
    15.    Similarly AsymptoteValue is a gradient or tangent at AsymptoteSlip, NOT (as documented) a value on the curve.
    16. 2. The curve has strange sudden high values when slip < 0.5 m/s.  This may be a PhysX kludge to stop low-speed sliding
    17.    due to an incorrect zero friction gradient at the origin, (x,y) = (0,0)
    18. 3. The correct default stiffness setting is 760.  This value gives no scaling of curve parameters.    
    19. 4. While you can experiment with settings to get a car to slide OK, you can't simulate reality since no settings will
    20.    give the curves given in the literature or the PhysX/Unity documentation.
    21.    Hopefully NVIDIA will create a new API function with a correct implementation!  
    22. A proper implementation of the intended hermetic cubic splines will fix all these problems.
    23. (Alternatively a piecewise linear curve (of 4 pieces) would probably do a good job.)
    24. */
    25. /*
    26. This is a script to plot Wheel Friction Curves.  
    27. The curves are plots of fricitionCoefficient [N/N] vs slip [m/s].
    28. For a given sideSlip it calculates the sideForce of the tire.
    29. Note: Side force on the tire = hit.Force * frictionCoeff; where 'hit' is hit data from GetGroundHit (hit)
    30. The shape of the curve is set by cubic spline control tangents (extremumSlip,extremumValue) and (asymptoteSlip,asymptoteValue).
    31. (Note: Contrary to the documentation extremumValue and asymptoteValue are gradients [s/m] NOT friction coefficient values [N/N].)
    32. How it works:
    33.  We applies a gradually increasing side force to a tire.  As the tire begins to slide it uses the acceleration of the
    34.  wheel to calculate the skidding reaction force of the tire.
    35. How to use it:
    36.  1. Create a plane and set the Transform Scale to (100,1,100)
    37.  2. Create empty an GameObject at location (0,0,0). Add this script to the object.
    38.  3. In Inspector choose your settings for PhysX Extremum Value etc.
    39.  4. Run the script.
    40.  5. When it stops set Physx Stiffness = the Reference Stiffness shown in the Inspector and rerun.
    41.  6. Look for a file wfcXXXXXXX.csv in the project directory and plot first two columns using
    42.     Gnuplot (or Excel).
    43.  7. (A gnuplot script to plot the curves and experiment with a better cspline curve is given below.)
    44.  
    45. Neil Temperley 25 Aug 2012.
    46. */
    47. public class FrictionTest : MonoBehaviour
    48. {
    49.     // INPUTS
    50.     public float PhysxExtremumSlip = 0.6f; // [m/s]
    51.     public float PhysxExtremumValue = 1.2f; // [s/m] GRADIENT!!
    52.     public float PhysxAsymptoteSlip = 2.0f; // [m/s]
    53.     public float PhysxAsymptoteValue = 0.2f; // [s/m] GRADIENT!!
    54.     public float PhysxStiffness = 760.0f; // [-]
    55.     public int solverIterationCount = 6; // [-] Input. In some cases value affects correct PhysxStiffness!?
    56.     private float forceIncreaseFactor = 1.05f; // If wheel slows, force is increased by this factor.
    57.     private int skipCountMax = 2; // number of readings skipped after force increase.
    58.     // OUTPUTS:
    59.     public float pushForce = 300.0f; // [N] Input/Output: starting value
    60.     public float reactionForce = 0.0f; // [N]
    61.     public float sidewaysSlipOld = 0.0f; // [m/s]
    62.     public float frictionCoeff = 0.0f; // [N/N]
    63.     private float frictionCoeffOld = 0.0f; // [N/N]
    64.     public float frictionCoeffSlope = 0.0f; // [s/m]
    65.     public float acceleration = 0.0f; // [m/s^2]
    66.     public float referenceStiffness = 0.0f; // [-] Only valid at end.
    67.     public float actualExtremumSlip; // [m/s] Only valid at end.
    68.     public float actualExtremumFrictionCoeff; // [N/N] Only valid at end.
    69.     public string filename;           // Output filename
    70.     public int skipCount = 0;
    71.     private StreamWriter sw;
    72.     public GameObject wheel;
    73.     public Rigidbody wheelRigidBody;
    74.     public GameObject wheelObject;
    75.     public WheelCollider wheelCollider;
    76.     public WheelHit hit;
    77.  
    78.     void Start ()
    79.     {
    80.         wheel = GameObject.CreatePrimitive (PrimitiveType.Cylinder);
    81.         wheel.transform.localScale = new  Vector3 (0.25f, 0.08f, 0.25f);
    82. //      wheel = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    83. //      wheel.transform.localScale = new  Vector3(0.25f, 0.25f, 0.25f);
    84.         wheel.name = "Test Wheel";
    85.         wheel.transform.rotation = Quaternion.Euler (new Vector3 (-90.0f, -90.0f, 0.0f));
    86.         Destroy (wheel.collider);
    87.        
    88.         wheelRigidBody = wheel.AddComponent<Rigidbody> ();
    89.         wheelRigidBody.mass = 300.0f; //[kg]
    90.         wheelRigidBody.freezeRotation = true;
    91.         wheelRigidBody.sleepVelocity = 0.001f;
    92.  
    93.         wheelObject = new GameObject ();
    94.         wheelObject.transform.parent = wheel.transform;
    95.         wheelObject.transform.localScale = new Vector3 (1.0f, 1.0f, 1.0f);
    96.         wheelObject.transform.position = new Vector3 (0.0f, 0.0f, 0.0f);
    97.         wheelObject.transform.rotation = Quaternion.Euler (new Vector3 (0.0f, 0.0f, 0.0f));
    98.         wheelCollider = wheelObject.AddComponent<WheelCollider> ();
    99.         wheelCollider.radius = 0.5f;
    100.  
    101.         WheelFrictionCurve wfc = new WheelFrictionCurve ();
    102.         wfc.extremumSlip = PhysxExtremumSlip;
    103.         wfc.extremumValue = PhysxExtremumValue;
    104.         wfc.asymptoteSlip = PhysxAsymptoteSlip;
    105.         wfc.asymptoteValue = PhysxAsymptoteValue;
    106.         wfc.stiffness = PhysxStiffness;
    107.         Physics.solverIterationCount = solverIterationCount;
    108.         wheelCollider.sidewaysFriction = wfc;
    109.  
    110.         wheel.transform.position = new Vector3 (0.0f, 0.12f, 0.0f);
    111.        
    112.         filename = String.Format ("wfc-{0:F2}-{1:F2}-{2:F2}-{3:F2}-{4:F0}-{5:D}.csv",
    113.             wheelCollider.sidewaysFriction.extremumSlip, wheelCollider.sidewaysFriction.extremumValue,
    114.             wheelCollider.sidewaysFriction.asymptoteSlip, wheelCollider.sidewaysFriction.asymptoteValue,
    115.             wheelCollider.sidewaysFriction.stiffness, Physics.solverIterationCount);
    116.         actualExtremumSlip = 0.0f; // [m/s]
    117.         actualExtremumFrictionCoeff = 0.0f; // [N/N]
    118.  
    119.         skipCount = skipCountMax; // skip first two readings.
    120.         sw = new StreamWriter (filename);
    121.         sw.AutoFlush = true;
    122.         sw.WriteLine ("# Speed [m/s],Friction Coeff [N/N],pushForce [N],hit.force [N],extremumSlip [m/s],extremumValue [s/m],asymptoteSlip [m/s],asymptoteValue [s/m],stiffness [-],solverIterationCount [-]");
    123.  
    124.     }
    125.    
    126.  
    127.     // Update is called once per frame
    128.     void Update ()
    129.     {
    130.    
    131.     }
    132.    
    133.     void FixedUpdate ()
    134.     {
    135.         string outString;
    136.  
    137.         wheelRigidBody.AddForce (transform.right * pushForce);
    138.         if (wheelCollider.GetGroundHit (out hit)  // ) {
    139.             Mathf.Abs ((hit.force / (wheelRigidBody.mass * 9.81f)) - 1.0f) < 0.01f) {
    140. //          if (Mathf.Abs ((hit.force / (rigidbody.mass * 9.81f)) - 1.0f) > 0.005f) { // skip early points where wheel may bounce.
    141. //              Debug.Log("Large hit force!");
    142. //          }
    143. //          Debug.Log (String.Format ("hit.force = {0:F2}; rigidbody.mass * 9.81f = {1:F2}", hit.force, wheelRigidBody.mass * 9.81f));
    144. //          Debug.Log (String.Format ("hit.sidewaysSlip = {0:F2};", hit.sidewaysSlip));
    145.             acceleration = (hit.sidewaysSlip - sidewaysSlipOld) / Time.fixedDeltaTime;
    146.             reactionForce = pushForce - (wheelRigidBody.mass * acceleration);
    147.             frictionCoeff = reactionForce / hit.force;
    148.             frictionCoeffSlope = (frictionCoeff - frictionCoeffOld) / (hit.sidewaysSlip - sidewaysSlipOld); // noisy and not needed!
    149.  
    150.             if (skipCount <= 0) { // skip points that follow a force change.
    151.                 if (hit.sidewaysSlip < wheelCollider.sidewaysFriction.asymptoteSlip
    152.                    hit.sidewaysSlip >= 0.5
    153.                    frictionCoeff > actualExtremumFrictionCoeff) { // store point of maximum value:
    154.                     actualExtremumSlip = hit.sidewaysSlip;
    155.                     actualExtremumFrictionCoeff = frictionCoeff;
    156.                 }
    157.                 if (Mathf.Abs (acceleration) < 0.001f || acceleration < -0.1f) {
    158.                     outString = String.Format ("{0:F2},{1:F3},{2:F0},{3:F0},{4:F2},{5:F2},{6:F2},{7:F2},{8:F1},{9:D},Force Increase",
    159.                         hit.sidewaysSlip, frictionCoeff, pushForce, hit.force,
    160.                         wheelCollider.sidewaysFriction.extremumSlip, wheelCollider.sidewaysFriction.extremumValue,
    161.                         wheelCollider.sidewaysFriction.asymptoteSlip, wheelCollider.sidewaysFriction.asymptoteValue,
    162.                         wheelCollider.sidewaysFriction.stiffness, Physics.solverIterationCount);
    163.                     pushForce *= forceIncreaseFactor; // gently increase the force to keep traversing the friction curve.
    164.                     skipCount = skipCountMax; // skip next few readings which will suffer noise from force change.
    165.                 } else {
    166.                     outString = String.Format ("{0:F2},{1:F3},{2:F0},{3:F0},{4:F2},{5:F2},{6:F2},{7:F2},{8:F1},{9:D},,",
    167.                         hit.sidewaysSlip, frictionCoeff, pushForce, hit.force,
    168.                         wheelCollider.sidewaysFriction.extremumSlip, wheelCollider.sidewaysFriction.extremumValue,
    169.                         wheelCollider.sidewaysFriction.asymptoteSlip, wheelCollider.sidewaysFriction.asymptoteValue,
    170.                         wheelCollider.sidewaysFriction.stiffness, Physics.solverIterationCount);
    171.                 } // if()
    172.                 if (hit.sidewaysSlip < 2.0 * wheelCollider.sidewaysFriction.asymptoteSlip) {
    173.                     sw.WriteLine (outString); // write data to file.
    174.                     Debug.Log (outString);
    175.                 } else {
    176.                     // WARNING! This estimate of the correct value of stiffness only works with the current
    177.                     // incorrect PhysX implementation of the Wheel Friction Curve!!..
    178.                     referenceStiffness = hit.sidewaysSlip * wheelCollider.sidewaysFriction.asymptoteValue * wheelCollider.sidewaysFriction.stiffness / frictionCoeff;
    179.                     Debug.Log (String.Format ("Finished! Reference Stiffness = {0:F0}; Friction Coeff. Extremum = {1:F2} [N/N] at {2:F2} [m/s]; ",
    180.                     referenceStiffness, actualExtremumFrictionCoeff, actualExtremumSlip) + outString);
    181.                     // For best accuracy set stiffness = referenceStiffness and rerun the program!
    182.                     Debug.Break (); // stop.
    183.                 } // if()
    184.             } else {
    185.                 skipCount--;
    186.             } // if()
    187.            
    188.             sidewaysSlipOld = hit.sidewaysSlip;
    189.             frictionCoeffOld = frictionCoeff;
    190.         }
    191.     } // FixedUpdate ()
    192. } // FrictionTest
    193.  
    194.  
    195. // Gnuplot Script.  Cut and Paste this into a file wfc-gnuplot.plt in the Unity Project directory.
    196. /*
    197. # Gnuplot plot file to plot Wheel Friction Curves.
    198. # See http://www.gnuplot.info/
    199. # Usage:
    200. # Start gnuplot, then at gnuplot prompt:
    201. #  cd 'C:\Users\Neil\Documents\Unity\Wheel Friction Project'
    202. #  load "wfc-gnuplot.plt"
    203. # ======================================================================
    204. # Set these parameters to match the values you used in Unity and this gnuplot script will
    205. # open the correct filename:
    206. PhysxExtremumSlip   = 0.6
    207. PhysxExtremumValue  = 1.2
    208. PhysxAsymptoteSlip  = 2.0
    209. PhysxAsymptoteValue = 0.2
    210. PhysxStiffness      = 760
    211. solverIterationCount = 6
    212. # This value is internal to Unity:
    213. refStiffness   = 760
    214. # ----------------------------------------------------------------------
    215. sfe = sprintf("fe(x) = x * PhysxExtremumValue * PhysxStiffness/%0.0f", refStiffness)
    216. sfa = sprintf("fa(x) = x * PhysxAsymptoteValue * PhysxStiffness/%0.0f", refStiffness)
    217. sLabel = sprintf("PhysxExtremumSlip    = %0.2f [m/s]\nPhysxExtremumValue = %0.2f [s/m]\nPhysxAsymptoteSlip    = %0.2f [m/s]\nPhysxAsymptoteValue = %0.2f [s/m]\nPhysxStiffness = %0.0f [-]\nPhysics.solverIterationCount = %d [-]\n%s\n%s", PhysxExtremumSlip, PhysxExtremumValue, PhysxAsymptoteSlip, PhysxAsymptoteValue, PhysxStiffness, solverIterationCount, sfe, sfa)
    218. fileName = sprintf("wfc-%0.2f-%0.2f-%0.2f-%0.2f-%0.0f-%d",PhysxExtremumSlip, PhysxExtremumValue, PhysxAsymptoteSlip, PhysxAsymptoteValue, PhysxStiffness,solverIterationCount)
    219. wfc = sprintf("%s.csv",fileName)
    220. sGIFFilename = sprintf("%s.gif",fileName)
    221. print "Attempting to read this csv file: ", wfc
    222.  
    223. # ======================================================================
    224. # Here we show how a cspline could meet PhysX's design intention: Inputs are:
    225. # y0 = static friction coeff, (x1,y1) = Extremum Slip and Value; (x2,y2,m2) = Asymptote Slip, Value and Gradient.
    226. # Note: x0 = 0; m1 = 0; (x3,y3,m3) ensure straight line extrapolation after (x2,y2,m2).
    227. ## Inputs:
    228. # y0 = static friction coeff:
    229. y0 = 0.3
    230. # (x1,y1) = Extremum Slip and Value:
    231. x1 = 1.0
    232. y1 = 1.05
    233. # (x2,y2,m2) = Asymptote Slip, Value and Gradient:
    234. x2 = 2.0
    235. y2 = 0.8
    236. m2 = 0.0
    237. #
    238. ## Fixed or Derived Parameters.  Don't alter these:
    239. x0 = 0.0
    240. # m0: This is a reasonable estimate for the correct slope at (0,0):
    241. m0 = 1.2*(y1-y0)/(x1-x0)
    242. m1 = 0.0
    243. x3 = 2.0*x2
    244. y3 = y2 + m2 * (x3-x2)
    245. m3 = m2
    246. ## cspline formula.  See http://en.wikipedia.org/wiki/Cspline
    247. t(x,x0,x1) = (x-x0)/(x1-x0)
    248. h00(t) = (1. + 2.*t) * (1. - t)**2.0
    249. h10(t) = t * (1. - t)**2.0
    250. h01(t) = t*t * (3. - 2.*t)
    251. h11(t) = t*t * (t - 1.)
    252. # Note in gnuplot function args take precedence over globals with same name:
    253. cs(x,x0,y0,m0,x1,y1,m1) = h00(t(x,x0,x1))*y0 + h10(t(x,x0,x1))*(x1-x0)*m0 + h01(t(x,x0,x1))*y1 + h11(t(x,x0,x1))*(x1-x0)*m1
    254. cspline(x,y0,x1,y1,x2,y2,m2) = (x<x1)? cs(x,x0,y0,m0,x1,y1,m1) : (x<x2)? cs(x,x1,y1,m1,x2,y2,m2) : cs(x,x2,y2,m2,x3,y3,m3)
    255.  
    256. # ======================================================================
    257. # Default terminal:
    258. set terminal windows
    259. reset
    260. set size 1,1
    261. # Create a parameter for wait time [s] between plots. -1 means wait for keypress.
    262. wait=-1
    263. set grid
    264. # Don't show date/time
    265. unset time
    266. # Set no. of plot points for functions.  Default = 100
    267. set samples 200
    268.  
    269. # Style when ploting points from a file:
    270. set  style data linespoints
    271. #set style data points
    272.  
    273. set autoscale xy
    274. # ======================================================================
    275. # ---- DO PLOT 1 ----
    276. set title "PhysX/Unity Wheel Friction Curve"
    277. set xlabel "Slip Speed [m/s]"
    278. set ylabel "Friction Coefficient [N/N]"
    279. set xrange[0:]
    280. #set yrange[0:1.6]
    281. set yrange[0:]
    282. fe(x) = (x <= PhysxExtremumSlip)? x * PhysxExtremumValue  * (PhysxStiffness/refStiffness) : NaN
    283. fa(x) = (x >= PhysxExtremumSlip)? x * PhysxAsymptoteValue * (PhysxStiffness/refStiffness) : NaN
    284. set key at graph 0.90,0.97 right
    285. set label sLabel at graph 0.98,0.35 right
    286. # Input file contains comma-separated values fields
    287. set datafile separator ","
    288. #plot wfc lw 2, fe(x) lw 2, fa(x) lw 2, cspline(x,y0,x1,y1,x2,y2,m2)
    289. plot wfc lw 2, fe(x) lw 2, fa(x) lw 2
    290. pause wait "Press return to continue..."
    291. # ----------------------------------------
    292. # Generate a gif file of the above plot (default size for gif is 640x480)
    293. set terminal gif font arial 8 size 500,375
    294. set output sGIFFilename
    295. replot
    296. #pause 0 "Plot sGIFFilename  created!"
    297.  
    298. # Then reset for next plot:
    299. set terminal windows
    300. set size 1,1
    301. set output
    302. # ======================================================================
    303. # ---- DO PLOT 2 ----
    304. set title "Corrected cspline Wheel Friction Curve"
    305. set xrange[0:1.0*x3]
    306. unset label
    307. plot cspline(x,y0,x1,y1,x2,y2,m2) lw 2
    308. # ======================================================================
    309. */
    310.  
     
    Last edited: Oct 12, 2012
    Mikael-H likes this.
  2. nikescar

    nikescar

    Joined:
    Nov 16, 2011
    Posts:
    165
    Wow! This is some great detective work. I found the wheel colliders to be less than accurate as well.

    I'm gonna look into this at some point and try to make a more realistic wheel collider. Your script should make it a lot easier to make sure I'm heading in the right direction.

    Thanks for your work.
     
  3. grindhouse

    grindhouse

    Joined:
    Aug 2, 2013
    Posts:
    7
    is this problem still present on current release? 4.x ?
    if this is the case, trying to understand the documentation is futile and frustrating to say the least.
     
  4. r-kamphuis

    r-kamphuis

    Joined:
    Jan 22, 2015
    Posts:
    1
    even still present in unity 5.x...
     
  5. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Has anyone actually reported a bug with this?
     
  6. NDSno1

    NDSno1

    Joined:
    Dec 20, 2014
    Posts:
    223
    I'm sorry for digging this up, but I'm looking into this as well. I'm trying to extract tire force at contact point of wheel collider, and is confused about whether wheelhit.force is the tire force or the vertical normal force.
    Can someone tell me how to use the above code? There are many syntax error in the code (mostly in if statements) so I don't know what to do to test.
     
  7. Le-Pampelmuse

    Le-Pampelmuse

    Joined:
    Jan 2, 2015
    Posts:
    26
    I too just came about this thread because I'm very sick and tired of Untiy having the same broken wheelcolliders for a decade.
    The commented code is indeed very messed up, did you ever clean it up and got it to work?
    I'd rather not waste my time trying to decipher it if it doesnt work as advertised.