1. Help us improve the editor usability and artist workflows. Join our discussion to provide your feedback.
    Dismiss Notice
  2. Unity 5.6 is now released.
    Dismiss Notice
  3. Check out all the fixes for 5.5 in patch releases 1 & 2.
    Dismiss Notice
  4. Get further faster with the Unity Plus Accelerator Pack, free for new Unity Plus subscribers for a limited time. Click here for more details.
    Dismiss Notice
  5. Learn how you'll soon be able to publish your games to China in four simple steps with Xiaomi. Sign up now for early access.
    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.         wheelCollider.sidewaysFriction = wfc;
    108.  
    109.         wheel.transform.position = new Vector3 (0.0f, 0.12f, 0.0f);
    110.        
    111.         filename = String.Format ("wfc-{0:F2}-{1:F2}-{2:F2}-{3:F2}-{4:F0}-{5:D}.csv",
    112.             wheelCollider.sidewaysFriction.extremumSlip, wheelCollider.sidewaysFriction.extremumValue,
    113.             wheelCollider.sidewaysFriction.asymptoteSlip, wheelCollider.sidewaysFriction.asymptoteValue,
    114.             wheelCollider.sidewaysFriction.stiffness, Physics.solverIterationCount);
    115.         actualExtremumSlip = 0.0f; // [m/s]
    116.         actualExtremumFrictionCoeff = 0.0f; // [N/N]
    117.  
    118.         skipCount = skipCountMax; // skip first two readings.
    119.         sw = new StreamWriter (filename);
    120.         sw.AutoFlush = true;
    121.         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 [-]");
    122.  
    123.     }
    124.    
    125.  
    126.     // Update is called once per frame
    127.     void Update ()
    128.     {
    129.    
    130.     }
    131.    
    132.     void FixedUpdate ()
    133.     {
    134.         string outString;
    135.  
    136.         wheelRigidBody.AddForce (transform.right * pushForce);
    137.         if (wheelCollider.GetGroundHit (out hit)  // ) {
    138.             Mathf.Abs ((hit.force / (wheelRigidBody.mass * 9.81f)) - 1.0f) < 0.01f) {
    139. //          if (Mathf.Abs ((hit.force / (rigidbody.mass * 9.81f)) - 1.0f) > 0.005f) { // skip early points where wheel may bounce.
    140. //              Debug.Log("Large hit force!");
    141. //          }
    142. //          Debug.Log (String.Format ("hit.force = {0:F2}; rigidbody.mass * 9.81f = {1:F2}", hit.force, wheelRigidBody.mass * 9.81f));
    143. //          Debug.Log (String.Format ("hit.sidewaysSlip = {0:F2};", hit.sidewaysSlip));
    144.             acceleration = (hit.sidewaysSlip - sidewaysSlipOld) / Time.fixedDeltaTime;
    145.             reactionForce = pushForce - (wheelRigidBody.mass * acceleration);
    146.             frictionCoeff = reactionForce / hit.force;
    147.             frictionCoeffSlope = (frictionCoeff - frictionCoeffOld) / (hit.sidewaysSlip - sidewaysSlipOld); // noisy and not needed!
    148.  
    149.             if (skipCount <= 0) { // skip points that follow a force change.
    150.                 if (hit.sidewaysSlip < wheelCollider.sidewaysFriction.asymptoteSlip
    151.                    hit.sidewaysSlip >= 0.5
    152.                    frictionCoeff > actualExtremumFrictionCoeff) { // store point of maximum value:
    153.                     actualExtremumSlip = hit.sidewaysSlip;
    154.                     actualExtremumFrictionCoeff = frictionCoeff;
    155.                 }
    156.                 if (Mathf.Abs (acceleration) < 0.001f || acceleration < -0.1f) {
    157.                     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",
    158.                         hit.sidewaysSlip, frictionCoeff, pushForce, hit.force,
    159.                         wheelCollider.sidewaysFriction.extremumSlip, wheelCollider.sidewaysFriction.extremumValue,
    160.                         wheelCollider.sidewaysFriction.asymptoteSlip, wheelCollider.sidewaysFriction.asymptoteValue,
    161.                         wheelCollider.sidewaysFriction.stiffness, Physics.solverIterationCount);
    162.                     pushForce *= forceIncreaseFactor; // gently increase the force to keep traversing the friction curve.
    163.                     skipCount = skipCountMax; // skip next few readings which will suffer noise from force change.
    164.                 } else {
    165.                     outString = String.Format ("{0:F2},{1:F3},{2:F0},{3:F0},{4:F2},{5:F2},{6:F2},{7:F2},{8:F1},{9:D},,",
    166.                         hit.sidewaysSlip, frictionCoeff, pushForce, hit.force,
    167.                         wheelCollider.sidewaysFriction.extremumSlip, wheelCollider.sidewaysFriction.extremumValue,
    168.                         wheelCollider.sidewaysFriction.asymptoteSlip, wheelCollider.sidewaysFriction.asymptoteValue,
    169.                         wheelCollider.sidewaysFriction.stiffness, Physics.solverIterationCount);
    170.                 } // if()
    171.                 if (hit.sidewaysSlip < 2.0 * wheelCollider.sidewaysFriction.asymptoteSlip) {
    172.                     sw.WriteLine (outString); // write data to file.
    173.                     Debug.Log (outString);
    174.                 } else {
    175.                     // WARNING! This estimate of the correct value of stiffness only works with the current
    176.                     // incorrect PhysX implementation of the Wheel Friction Curve!!..
    177.                     referenceStiffness = hit.sidewaysSlip * wheelCollider.sidewaysFriction.asymptoteValue * wheelCollider.sidewaysFriction.stiffness / frictionCoeff;
    178.                     Debug.Log (String.Format ("Finished! Reference Stiffness = {0:F0}; Friction Coeff. Extremum = {1:F2} [N/N] at {2:F2} [m/s]; ",
    179.                     referenceStiffness, actualExtremumFrictionCoeff, actualExtremumSlip) + outString);
    180.                     // For best accuracy set stiffness = referenceStiffness and rerun the program!
    181.                     Debug.Break (); // stop.
    182.                 } // if()
    183.             } else {
    184.                 skipCount--;
    185.             } // if()
    186.            
    187.             sidewaysSlipOld = hit.sidewaysSlip;
    188.             frictionCoeffOld = frictionCoeff;
    189.         }
    190.     } // FixedUpdate ()
    191. } // FrictionTest
    192.  
    193.  
    194. // Gnuplot Script.  Cut and Paste this into a file wfc-gnuplot.plt in the Unity Project directory.
    195. /*
    196. # Gnuplot plot file to plot Wheel Friction Curves.
    197. # See http://www.gnuplot.info/
    198. # Usage:
    199. # Start gnuplot, then at gnuplot prompt:
    200. #  cd 'C:\Users\Neil\Documents\Unity\Wheel Friction Project'
    201. #  load "wfc-gnuplot.plt"
    202. # ======================================================================
    203. # Set these parameters to match the values you used in Unity and this gnuplot script will
    204. # open the correct filename:
    205. PhysxExtremumSlip   = 0.6
    206. PhysxExtremumValue  = 1.2
    207. PhysxAsymptoteSlip  = 2.0
    208. PhysxAsymptoteValue = 0.2
    209. PhysxStiffness      = 760
    210. solverIterationCount = 6
    211. # This value is internal to Unity:
    212. refStiffness   = 760
    213. # ----------------------------------------------------------------------
    214. sfe = sprintf("fe(x) = x * PhysxExtremumValue * PhysxStiffness/%0.0f", refStiffness)
    215. sfa = sprintf("fa(x) = x * PhysxAsymptoteValue * PhysxStiffness/%0.0f", refStiffness)
    216. 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)
    217. fileName = sprintf("wfc-%0.2f-%0.2f-%0.2f-%0.2f-%0.0f-%d",PhysxExtremumSlip, PhysxExtremumValue, PhysxAsymptoteSlip, PhysxAsymptoteValue, PhysxStiffness,solverIterationCount)
    218. wfc = sprintf("%s.csv",fileName)
    219. sGIFFilename = sprintf("%s.gif",fileName)
    220. print "Attempting to read this csv file: ", wfc
    221.  
    222. # ======================================================================
    223. # Here we show how a cspline could meet PhysX's design intention: Inputs are:
    224. # y0 = static friction coeff, (x1,y1) = Extremum Slip and Value; (x2,y2,m2) = Asymptote Slip, Value and Gradient.
    225. # Note: x0 = 0; m1 = 0; (x3,y3,m3) ensure straight line extrapolation after (x2,y2,m2).
    226. ## Inputs:
    227. # y0 = static friction coeff:
    228. y0 = 0.3
    229. # (x1,y1) = Extremum Slip and Value:
    230. x1 = 1.0
    231. y1 = 1.05
    232. # (x2,y2,m2) = Asymptote Slip, Value and Gradient:
    233. x2 = 2.0
    234. y2 = 0.8
    235. m2 = 0.0
    236. #
    237. ## Fixed or Derived Parameters.  Don't alter these:
    238. x0 = 0.0
    239. # m0: This is a reasonable estimate for the correct slope at (0,0):
    240. m0 = 1.2*(y1-y0)/(x1-x0)
    241. m1 = 0.0
    242. x3 = 2.0*x2
    243. y3 = y2 + m2 * (x3-x2)
    244. m3 = m2
    245. ## cspline formula.  See http://en.wikipedia.org/wiki/Cspline
    246. t(x,x0,x1) = (x-x0)/(x1-x0)
    247. h00(t) = (1. + 2.*t) * (1. - t)**2.0
    248. h10(t) = t * (1. - t)**2.0
    249. h01(t) = t*t * (3. - 2.*t)
    250. h11(t) = t*t * (t - 1.)
    251. # Note in gnuplot function args take precedence over globals with same name:
    252. 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
    253. 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)
    254.  
    255. # ======================================================================
    256. # Default terminal:
    257. set terminal windows
    258. reset
    259. set size 1,1
    260. # Create a parameter for wait time [s] between plots. -1 means wait for keypress.
    261. wait=-1
    262. set grid
    263. # Don't show date/time
    264. unset time
    265. # Set no. of plot points for functions.  Default = 100
    266. set samples 200
    267.  
    268. # Style when ploting points from a file:
    269. set  style data linespoints
    270. #set style data points
    271.  
    272. set autoscale xy
    273. # ======================================================================
    274. # ---- DO PLOT 1 ----
    275. set title "PhysX/Unity Wheel Friction Curve"
    276. set xlabel "Slip Speed [m/s]"
    277. set ylabel "Friction Coefficient [N/N]"
    278. set xrange[0:]
    279. #set yrange[0:1.6]
    280. set yrange[0:]
    281. fe(x) = (x <= PhysxExtremumSlip)? x * PhysxExtremumValue  * (PhysxStiffness/refStiffness) : NaN
    282. fa(x) = (x >= PhysxExtremumSlip)? x * PhysxAsymptoteValue * (PhysxStiffness/refStiffness) : NaN
    283. set key at graph 0.90,0.97 right
    284. set label sLabel at graph 0.98,0.35 right
    285. # Input file contains comma-separated values fields
    286. set datafile separator ","
    287. #plot wfc lw 2, fe(x) lw 2, fa(x) lw 2, cspline(x,y0,x1,y1,x2,y2,m2)
    288. plot wfc lw 2, fe(x) lw 2, fa(x) lw 2
    289. pause wait "Press return to continue..."
    290. # ----------------------------------------
    291. # Generate a gif file of the above plot (default size for gif is 640x480)
    292. set terminal gif font arial 8 size 500,375
    293. set output sGIFFilename
    294. replot
    295. #pause 0 "Plot sGIFFilename  created!"
    296.  
    297. # Then reset for next plot:
    298. set terminal windows
    299. set size 1,1
    300. set output
    301. # ======================================================================
    302. # ---- DO PLOT 2 ----
    303. set title "Corrected cspline Wheel Friction Curve"
    304. set xrange[0:1.0*x3]
    305. unset label
    306. plot cspline(x,y0,x1,y1,x2,y2,m2) lw 2
    307. # ======================================================================
    308. */
    309.  
     
    Last edited: Oct 12, 2012
  2. nikescar

    nikescar

    Joined:
    Nov 16, 2011
    Posts:
    32
    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 Moderator

    Joined:
    Apr 11, 2010
    Posts:
    18,704
    Has anyone actually reported a bug with this?