Combined Longitudinal, Lateral, and Yawing Tire Slip Forces

David Savinkoff <[email protected]> Mon, 14 Nov 2016 18:10:40 -0700 (MST)
Newsgroups gmane.games.torcs.general
Message-ID <606977550.195947620.1479172240466.JavaMail.zimbra@mailid.telus.net>
Combined Longitudinal, Lateral, and Yawing Tire Slip Forces:

Hear ye NASA to NASCAR, tire makers, and all:
TORCS has it now! donuts (Nobody considered of yaw slippage!)

Nowhere could I find any mention of Combined Longitudinal, Lateral,
and Yawing Tire Slip Forces. Unless it can be shown otherwise ...
I will take credit for this discovery; and hereby disclose this
information, code, and method to the public domain so that it
is useful to everyone, and cannot be patented.

Sincerely,
David Savinkoff
November 14 2016

See patch for information:
  // COMBINED LONGITUDINAL, LATERAL, and YAWING TIRE SLIP FORCES:
  // Normalize the friction circle to get normalized vector components (sx, sy) while
  // braking (vt), accelerating (wrl), or wheel counter spin (vt - wrl) for any angle.
  // saz slip speed accounts for rotating the contact patch (yaw).

  saz = (1.1f*3.1416f) * car->DynGC.vel.az * car->carElt->_tireWidth(index);

  slip_magnitude = 0.000001f + /* Softening factor 0.000001 prevents divide by zero */
    sqrt( MAX(MAX((v2 + saz*saz), (vn*vn + wrl*wrl + saz*saz)), (vn*vn + (vt - wrl)*(vt - wrl) + saz*saz)) );

  sx = (vt - wrl) / slip_magnitude;
  sy = vn / slip_magnitude;
  saz /= slip_magnitude;

  s = sqrtf(sx*sx+sy*sy+saz*saz);

where:
wrl is the surface speed of the spinning wheel
vt is the axle speed over the ground in the direction the wheel is spinning
vn is wheel side slip speed
saz is wheel yaw slip speed (imagine steering a wheel with the vehicle stopped)
sx is normalized x-axis slip
sy is normalized y-axis slip
saz/slip_magnitude is normalized yaw slip
slip_magnitude normalizes the combined slip to be no greater than 1.0
s is COMBINED and NORMALIZED LONGITUDINAL, LATERAL, and YAW SLIP
Note that this method can be applied in slip displacement or slip speed (shown).

------------------------------------------------------------------------------

_______________________________________________
Torcs-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/torcs-users
torcs-1.3.7.2016nov14.diff (text/x-patch, 13 KB)
--- torcs-1.3.7/src/modules/graphic/ssggraph/CarSoundData.cpp	2014-02-10 03:16:18.000000000 -0800
+++ torcs-1.3.7/src/modules/graphic/ssggraph/CarSoundData.cpp	2016-10-09 17:42:50.000000000 -0700
@@ -315,29 +315,30 @@
 
     }
 
+// Calculate road-tire contact noise positions with an efficient formula.
+// note that sin(az) is nearly equal to az [in radians].
+// note that cos(az) is close to 1.0 for moderate yaw angles.
+// note that the contact patch is beneath GC (approx -0.3 metres).
+    tdble az = car->_yaw;
     for (i = 0; i<4; i++) {
-        tdble az = car->_yaw;
-        tdble Sinz = sin(az);
-        tdble Cosz = cos(az);
-                
         tdble x = car->priv.wheel[i].relPos.x;
         tdble y = car->priv.wheel[i].relPos.y;
                 
-        tdble dx = x * Cosz - y * Sinz;
-        tdble dy = x * Sinz + y * Cosz;
+        tdble dx = x - y*az; // x*cos(az) - y*sin(az)
+        tdble dy = x*az + y;
                 
         tdble dux = -car->_yaw_rate * y;
         tdble duy = car->_yaw_rate * x;
                 
-        dux = dux * Cosz - duy * Sinz;
-        duy = dux * Sinz + duy * Cosz;
+        dux = dux - duy*az;
+        duy = dux*az + duy;
                 
         wheel[i].u[0] = car->pub.DynGCg.vel.x + dux;
         wheel[i].u[1] = car->pub.DynGCg.vel.y + duy;
         wheel[i].u[2] = car->pub.DynGCg.vel.z;
         wheel[i].p[0] = car->pub.DynGCg.pos.x + dx;
         wheel[i].p[1] = car->pub.DynGCg.pos.y + dy;
-        wheel[i].p[2] = car->pub.DynGCg.pos.z;
+        wheel[i].p[2] = car->pub.DynGCg.pos.z - 0.3f;
     }
 }
 
--- torcs-1.3.7/src/modules/graphic/ssggraph/grskidmarks.cpp	2014-02-10 03:09:36.000000000 -0800
+++ torcs-1.3.7/src/modules/graphic/ssggraph/grskidmarks.cpp	2016-10-09 17:42:50.000000000 -0700
@@ -199,11 +199,7 @@
 			}
 		}
 
-		if (car->_skid[i] > 0.1f) {
-			cur_clr[3] = tanh(skid_sensitivity*car->_skid[i]);
-		} else {
-			cur_clr[3] = 0.0f;
-		}
+		cur_clr[3] = skid_sensitivity*car->_skid[i]; // linear (tanh removed, see wheel.cpp)
 
 		for (int c = 0; c < 3; c++) {
 			tdble tmp = grCarInfo[car->index].skidmarks->strips[i].smooth_colour[c];
@@ -216,7 +212,7 @@
 		}
 
 		if (car->pub.speed > 1.0f) {
-	    	if (cur_clr[3] > 0.1f) {
+	    	if (cur_clr[3] > 0.1f) { // 0.06 works better than 0.1 // something here improves traction ?!
                 
 				basevtx = new ssgVertexArray(4 * 2 + 1);
 				tdble sling_left = 0.0f;
--- torcs-1.3.7/src/modules/simu/simuv2/car.cpp	2016-05-16 15:53:07.000000000 -0700
+++ torcs-1.3.7/src/modules/simu/simuv2/car.cpp	2016-11-14 13:37:10.000000000 -0800
@@ -64,7 +64,7 @@
 	}
 	k = k * k;
 	car->Iinv.x = 12.0 / (car->mass * (car->dimension.y * car->dimension.y + car->dimension.z * car->dimension.z));
-	car->Iinv.y = 12.0 / (car->mass * (car->dimension.x * car->dimension.x + car->dimension.z * car->dimension.z));
+	car->Iinv.y = 12.0 / (car->mass * (k * car->dimension.x * car->dimension.x + car->dimension.z * car->dimension.z));
 	car->Iinv.z = 12.0 / (car->mass * (car->dimension.y * car->dimension.y + k * car->dimension.x * car->dimension.x));
 	
 	/* configure components */
@@ -146,7 +146,6 @@
 	tdble	m, w, minv;
 	tdble	SinTheta;
 	tdble	Cosz, Sinz;
-	tdble	v, R, Rv, Rm, Rx, Ry;
 	
 	Cosz = car->Cosz = cos(car->DynGCg.pos.az);
 	Sinz = car->Sinz = sin(car->DynGCg.pos.az);
@@ -199,41 +198,18 @@
 		F.M.y -= car->aero.lift[i] * (car->axle[i].xpos - car->statGC.x);
 	}
 	
-	/* Rolling Resistance */
-	v = sqrt(car->DynGCg.vel.x * car->DynGCg.vel.x + car->DynGCg.vel.y * car->DynGCg.vel.y);
-	R = 0;
-	for (i = 0; i < 4; i++) {
-		R += car->wheel[i].rollRes;
-	}
-	if (v > 0.00001) {
-		Rv = R / v;
-		if ((Rv * minv * SimDeltaTime) > v) {
-			Rv = v * m / SimDeltaTime;
-		}
-	} else {
-		Rv = 0;
-	}
-	Rx = Rv * car->DynGCg.vel.x;
-	Ry = Rv * car->DynGCg.vel.y;
-	
-	if ((R * car->wheelbase / 2.0 * car->Iinv.z) > fabs(car->DynGCg.vel.az)) {
-		Rm = car->DynGCg.vel.az / car->Iinv.z;
-	} else {
-		Rm = SIGN(car->DynGCg.vel.az) * R * car->wheelbase / 2.0;
-	}
-	
 	/* compute accelerations */
 	car->DynGC.acc.x = F.F.x * minv;
 	car->DynGC.acc.y = F.F.y * minv;
 	car->DynGC.acc.z = F.F.z * minv;
 	
-	car->DynGCg.acc.x = (F.F.x * Cosz - F.F.y * Sinz - Rx) * minv;
-	car->DynGCg.acc.y = (F.F.x * Sinz + F.F.y * Cosz - Ry) * minv;
+	car->DynGCg.acc.x = (F.F.x * Cosz - F.F.y * Sinz) * minv;
+	car->DynGCg.acc.y = (F.F.x * Sinz + F.F.y * Cosz) * minv;
 	car->DynGCg.acc.z = car->DynGC.acc.z;
 	
 	car->DynGCg.acc.ax = car->DynGC.acc.ax = F.M.x * car->Iinv.x;
 	car->DynGCg.acc.ay = car->DynGC.acc.ay = F.M.y * car->Iinv.y;
-	car->DynGCg.acc.az = car->DynGC.acc.az = (F.M.z - Rm) * car->Iinv.z;
+	car->DynGCg.acc.az = car->DynGC.acc.az += F.M.z * car->Iinv.z; // += see torcs/src/modules/simu/simuv2/simu.cpp :: SimUpdate() 
 }
 
 static void
@@ -256,7 +232,15 @@
 	if (fabs(car->DynGCg.vel.az) > 9.0) {
 		car->DynGCg.vel.az = SIGN(car->DynGCg.vel.az) * 9.0;
 	}
-		
+	
+	/* FIXME: Hack to stop stationary car from rotating */
+	if ((	car->DynGCg.vel.x*car->DynGCg.vel.x +
+		car->DynGCg.vel.y*car->DynGCg.vel.y +
+		car->DynGCg.vel.z*car->DynGCg.vel.z) < 0.01f )
+	{
+		car->DynGCg.vel.ax = car->DynGCg.vel.ay = car->DynGCg.vel.az = 0.0f;
+	}
+	
 	car->DynGC.vel.ax = car->DynGCg.vel.ax;
 	car->DynGC.vel.ay = car->DynGCg.vel.ay;
 	car->DynGC.vel.az = car->DynGCg.vel.az;
--- torcs-1.3.7/src/modules/simu/simuv2/collide.cpp	2014-04-12 06:55:29.000000000 -0700
+++ torcs-1.3.7/src/modules/simu/simuv2/collide.cpp	2016-10-09 18:17:35.000000000 -0700
@@ -37,7 +37,7 @@
 	for (i = 0; i < 4; i++) {
 		wheel = &(car->wheel[i]);
 		if (wheel->state & SIM_SUSP_COMP) {
-			car->DynGCg.pos.z += wheel->susp.spring.packers - wheel->rideHeight;
+			car->DynGCg.pos.z += 0.25f*(wheel->susp.spring.packers - wheel->rideHeight); // leverage of 4 wheels
 			RtTrackSurfaceNormalL(&(wheel->trkPos), &normal);
 			dotProd = (car->DynGCg.vel.x * normal.x + car->DynGCg.vel.y * normal.y + car->DynGCg.vel.z * normal.z) * wheel->trkPos.seg->surface->kRebound;
 			if (dotProd < 0.0f) {
--- torcs-1.3.7/src/modules/simu/simuv2/simu.cpp	2016-10-14 21:50:11.000000000 -0700
+++ torcs-1.3.7/src/modules/simu/simuv2/simu.cpp	2016-11-13 21:29:01.000000000 -0800
@@ -380,6 +380,7 @@
 				SimAxleUpdate(car, i);
 			}
 			CHECK(car);
+			car->DynGC.acc.az = 0.0f; // see torcs/src/modules/simu/simuv2/wheel.cpp :: SimWheelUpdateForce()
 			for (i = 0; i < 4; i++){
 				SimWheelUpdateForce(car, i);
 			}
--- torcs-1.3.7/src/modules/simu/simuv2/susp.cpp	2016-03-19 07:47:05.000000000 -0700
+++ torcs-1.3.7/src/modules/simu/simuv2/susp.cpp	2016-10-09 18:17:35.000000000 -0700
@@ -107,9 +107,9 @@
 	}
 	
 	susp->x *= susp->spring.bellcrank;
-	if (susp->x > susp->spring.xMax) {
+	if (susp->x >= susp->spring.xMax) { // Tested OK with '>=' (SD r6020)
 		susp->x = susp->spring.xMax;
-		susp->state = SIM_SUSP_EXT;
+		susp->state = SIM_SUSP_EXT; // susp->state updated also
 	}
 }
 
--- torcs-1.3.7/src/modules/simu/simuv2/wheel.cpp	2016-05-16 15:53:07.000000000 -0700
+++ torcs-1.3.7/src/modules/simu/simuv2/wheel.cpp	2016-11-14 14:14:08.000000000 -0800
@@ -46,8 +46,8 @@
 	Ca                    = GfParmGetNum(hdle, WheelSect[index], PRM_CA, (char*)NULL, 30.0f);
 	RFactor               = GfParmGetNum(hdle, WheelSect[index], PRM_RFACTOR, (char*)NULL, 0.8f);
 	EFactor               = GfParmGetNum(hdle, WheelSect[index], PRM_EFACTOR, (char*)NULL, 0.7f);
-	wheel->lfMax          = GfParmGetNum(hdle, WheelSect[index], PRM_LOADFMAX, (char*)NULL, 1.6f);
-	wheel->lfMin          = GfParmGetNum(hdle, WheelSect[index], PRM_LOADFMIN, (char*)NULL, 0.8f);
+	wheel->lfMax          = GfParmGetNum(hdle, WheelSect[index], PRM_LOADFMAX, (char*)NULL, 1.4f);
+	wheel->lfMin          = GfParmGetNum(hdle, WheelSect[index], PRM_LOADFMIN, (char*)NULL, 1.0f);
 	wheel->opLoad         = GfParmGetNum(hdle, WheelSect[index], PRM_OPLOAD, (char*)NULL, wheel->weight0 * 1.2f);
 	wheel->mass           = GfParmGetNum(hdle, WheelSect[index], PRM_MASS, (char*)NULL, 20.0f);
 
@@ -174,12 +174,12 @@
 {
 	tWheel *wheel = &(car->wheel[index]);
 	tdble axleFz = wheel->axleFz;
-	tdble vt, v, v2, wrl; // wheel related velocity
+	tdble vt, vn, v, v2, wrl, slip_magnitude = 0.000000f; // wheel related velocity
 	tdble Fn, Ft;
 	tdble waz;
-	tdble CosA, SinA;
-	tdble s, sa, sx, sy; // slip vector
-	tdble stmp, F, Bx;
+	double CosA, SinA;
+	tdble s, sx, sy, saz; // slip vector
+	tdble F, Bx;
 	tdble mu;
 	wheel->state = 0;
 
@@ -209,52 +209,51 @@
 	}
 
 	// HORIZONTAL FORCES
-	waz = wheel->steer + wheel->staticPos.az;
+	waz = wheel->steer + wheel->staticPos.az - (SimDeltaTime*car->DynGC.vel.az /* account for yaw induced wheel direction */);
 	CosA = cos(waz);
 	SinA = sin(waz);
 
 	// tangent velocity.
 	vt = wheel->bodyVel.x * CosA + wheel->bodyVel.y * SinA;
+	vn = wheel->bodyVel.y * CosA - wheel->bodyVel.x * SinA;
 	v2 = wheel->bodyVel.x * wheel->bodyVel.x + wheel->bodyVel.y * wheel->bodyVel.y;
-	v = sqrt(v2);
+	v = sqrtf(v2);
 
-	// slip angle
-	if (v < 0.000001f) {
-		sa = 0.0f;
-	} else {
-		sa = atan2(wheel->bodyVel.y, wheel->bodyVel.x) - waz;
+	wrl = wheel->spinVel * wheel->radius;
+
+	if (fabsf(vt - wrl) <= 0.1f) { // Adds numerical stability by catching traction
+		vt = wrl;
 	}
-	NORM_PI_PI(sa);
 
-	wrl = wheel->spinVel * wheel->radius;
 	if ((wheel->state & SIM_WH_ONAIR) != 0) {
-		sx = sy = 0.0f;
-	} else if (v < 0.000001f) {
-		sx = wrl;
-		sy = 0.0f;
+		sx = sy = saz = 0.0f;
 	} else {
-		sx = (vt - wrl) / fabs(vt);
-		sy = sin(sa);
+		// COMBINED LONGITUDINAL, LATERAL, and YAWING TIRE SLIP FORCES:
+		// Normalize the friction circle to get normalized vector components (sx, sy) while
+		// braking (vt), accelerating (wrl), or wheel counter spin (vt - wrl) for any angle.
+		// saz slip speed accounts for rotating the contact patch (yaw).
+		saz = (1.1f*3.1416f) * car->DynGC.vel.az * car->carElt->_tireWidth(index);
+		slip_magnitude = 0.000001f + /* Softening factor 0.000001 prevents divide by zero */
+				sqrt( MAX(MAX((v2 + saz*saz), (vn*vn + wrl*wrl + saz*saz)), (vn*vn + (vt - wrl)*(vt - wrl) + saz*saz)) );
+		sx = (vt - wrl) / slip_magnitude;
+		sy = vn / slip_magnitude;
+		saz /= slip_magnitude;
 	}
 
-	Ft = 0.0f;
-	Fn = 0.0f;
-	s = sqrt(sx*sx+sy*sy);
+	s = sqrtf(sx*sx+sy*sy+saz*saz);
 
 	{
-		// calculate _skid and _reaction for sound.
-		if (v < 2.0f) {
+		// calculate _skid and _reaction for sound (suspiciously; affects traction also).
+		if ((v < 0.01f) || (zforce < 0.002f)) {
 			car->carElt->_skid[index] = 0.0f;
 		} else {
-			car->carElt->_skid[index] =  MIN(1.0f, (s*zforce*0.0002f));
+			car->carElt->_skid[index] = s; // linear (tanh removed in grskidmarks.cpp)
 		}
 	}
 
-	stmp = MIN(s, 1.5f);
-	
-	// MAGIC FORMULA
-	Bx = wheel->mfB * stmp;
-	F = sin(wheel->mfC * atan(Bx * (1.0f - wheel->mfE) + wheel->mfE * atan(Bx))) * (1.0f + stmp * simSkidFactor[car->carElt->_skillLevel]);
+	// MAGIC FORMULA // Note: F = sin(...) below returns normalized force!
+	Bx = wheel->mfB * s;
+	F = sin(wheel->mfC * atan(Bx * (1.0f - wheel->mfE) + wheel->mfE * atan(Bx))) * (1.0f + s * simSkidFactor[car->carElt->_skillLevel]);
 
 	// load sensitivity
 	mu = wheel->mu * (wheel->lfMin + (wheel->lfMax - wheel->lfMin) * exp(wheel->lfK * zforce / wheel->opLoad));
@@ -262,31 +261,35 @@
 	F *= zforce * mu * wheel->trkPos.seg->surface->kFriction * (1.0f + 0.05f * sin(-wheel->staticPos.ax * 18.0f));	/* coeff */
 
 	wheel->rollRes = zforce * wheel->trkPos.seg->surface->kRollRes;
-    car->carElt->priv.wheel[index].rollRes = wheel->rollRes;
 
-	if (s > 0.000001f) {
-		// wheel axis based
-		Ft -= F * sx / s;
-		Fn -= F * sy / s;
-	}
+	// wheel axis based
+	s += 0.000001f; // add 0.000001 to avoid divide by zero later.
+	Ft = -(F - wheel->rollRes) * sx/s; // rolling resistance is the rolling vector component only.
+	Fn = -F * sy/s;
 
-	RELAXATION2(Fn, wheel->preFn, 50.0f);
-	RELAXATION2(Ft, wheel->preFt, 50.0f);
+	car->DynGC.acc.az += -F * saz/s * SimDeltaTime * car->Iinv.z; // see torcs/src/modules/simu/simuv2/simu.cpp :: SimUpdate()
+
+	wheel->rollRes *= fabsf(sx/s); // not used (removed from /src/modules/simu/simuv2/car.cpp)
+	car->carElt->priv.wheel[index].rollRes = wheel->rollRes; // not used in /src/modules/graphic/ssggraph/CarSoundData.cpp
+
+	waz = wheel->steer + wheel->staticPos.az; /* don't include yaw induced wheel direction here */
+	CosA = cos(waz);
+	SinA = sin(waz);
 
 	wheel->relPos.az = waz;
 
 	wheel->forces.x = Ft * CosA - Fn * SinA;
 	wheel->forces.y = Ft * SinA + Fn * CosA;
 	wheel->spinTq = Ft * wheel->radius;
-	wheel->sa = sa;
+	wheel->sa = atan2(wheel->bodyVel.y, wheel->bodyVel.x) - waz;
 	wheel->sx = sx;
 
 	wheel->feedBack.spinVel = wheel->spinVel;
 	wheel->feedBack.Tq = wheel->spinTq;
 	wheel->feedBack.brkTq = wheel->brake.Tq;
 
-	car->carElt->_wheelSlipSide(index) = sy*v;
-	car->carElt->_wheelSlipAccel(index) = sx*v;
+	car->carElt->_wheelSlipSide(index) = vn;
+	car->carElt->_wheelSlipAccel(index) = MAX(vt, wrl); // What is wheelSlipAccel ?
 	car->carElt->_reaction[index] = zforce;
 }
 
@@ -301,8 +304,6 @@
 		wheel = &(car->wheel[i]);
 		wheel->spinVel = wheel->in.spinVel;
 
-		RELAXATION2(wheel->spinVel, wheel->prespinVel, 50.0f);
-
 		wheel->relPos.ay += wheel->spinVel * SimDeltaTime;
 		NORM_PI_PI(wheel->relPos.ay);
 		car->carElt->_wheelSpinVel(i) = wheel->spinVel;