GDSim v0.4a - Autocross and Custom Setups

  • Thread starter Wolfe
  • 62 comments
  • 34,624 views
Out of curiosity, have you ever tested your wheel system with a big-wheeler, like an ATV or a monster truck?

My curiosity stems from having seen numerous implementations of wheels and 99.9% of wheels go through things a bit before the suspension raises the wheel above the thing (e.g. sidewalks, road bumps, etc). But this is mostly with raycast type of wheel implementations. So I'm wondering how yours would fare.


(EDIT: I played the game and tried out the pickup truck. That answered my question (it seems to fare pretty well). I couldn't play before because my gfx card didn't support GLES3, but I got a new card recently.)
 
Last edited:
I had good luck with my clutch simulation. :) It's not totally accurate -- clutch slip and its effect on the engine are ultimately based on clutch pedal position, not torques -- but it is working well enough. You can now:
  • Slip the clutch to set off
  • Spin the drivewheels from a start (notable improvement over the example I had followed before)
  • Stall the engine, or turn the ignition off (just because)
  • Bump-start the engine, or restart it with input (I've arranged it with flooring the throttle, separate from ignition, no starter button required)
Getting bump-starting to work was fun! Once I got the fundamental clutch model to work, it was relatively easy to add an auto-clutch, which disables stalling (and ignition control, at least for now) and approximates aggressive clutch slip for effective launches. With the auto-clutch behavior in place, I implemented an automatic transmission -- as in an automatic with basic automatic-like shift logic, not a manual that shifts itself.

On the short term to-do list is a suitable test track/area worth driving around in, so I can start to hone in on reasonable values for the tire model, which is on the slippery side right now. If that works out, the plan is that an upload of some kind won't be far behind.


III. Rays Go Round - Lateral Force

Now that we have a suspension, it's time to simulate some rubber beneath it. Before you can do that, there is some information to keep track of:
  1. The wheel's "local" velocity
  2. The wheel's "Z velocity", which is just the forward/back component of its local velocity (I have reasons to store this separately)
  3. What I call the wheel's "planar vector" -- a convenient normalized/unit vector that excludes vertical motion
  4. The wheel's last position, because the SpringArm is not a physics object and Godot does not calculate its velocity automatically
To get these, we do a little bit of vector math. If you're lost, don't feel bad -- I had to reacquaint myself. :lol: This "cheat sheet" might help. I've collected the variables like this:
Code:
    var local_vel = global_transform.basis.xform_inv((global_transform.origin - prev_pos) / delta)
    z_vel = -local_vel.y
    var planar_vect = Vector2(local_vel.x, local_vel.y).normalized()
    prev_pos = global_transform.origin
Note that the 'Z' velocity is the 'Y' component of the full vector. That's one reason I give it another variable -- for clarity -- and a consequence of rotating the SpringArm node, as I mentioned in part one. Negative Z is forward in Godot, so we pull it from negative Y here.

As I've mentioned, I have a "brush" tire formula I'm using, but I think it would be more informative to start with a simplified Pacejka formula first -- which is what I started out with. The Pacejka formula can be easily plotted to a graph, which will help explain how tires work and what we're tinkering with.

"Pacejka?" -- The Pacejka formula is an equation concocted to fit empirical data collected from testing real tires. It is named for Hans Pacejka, one of the authors of the formula. A sim developer called Edy, who sells a physics package in the Unity asset store, provides a helpful page going into depth on the 1994 version of the Pacejka formula as well as a overview of the model. In short, it is an effective but unwieldy formula to use, with little guidance on good parameters.

To make use of any tire formula -- it doesn't have to be Pacejka or the brush formula -- we have a bit more necessary information to collect: the tire's slip angle and slip ratio.

Slip Angle
I suppose many GTPlanet members will be familiar with slip angle, but don't worry if you're not -- it is the angle between the direction the tire is pointing and the direction it's rolling. I have another link here for reference. :)

There are multiple ways to derive the slip angle. My method makes use of the "planar vector" from the beginning. I've called this variable "x_slip" to denote lateral slip:
Code:
var x_slip = asin(clamp(-planar_vect.x, -1, 1))

That's all. The arcsine of the (negative) x component of the vector, with a check to make sure its floating point value doesn't slightly exceed -1 or 1, which is undefined in an arcsine function (oops).

Slip Ratio
The slip ratio compares the angular velocity of the wheel (I call it "spin" for short) with how quickly the road is passing by beneath it -- to put it simply, wheelspin or locking the wheels. There are multiple definitions for the slip ratio; I'm using one where locking the brakes equates to -1 if you're moving forward, and +1 if you're moving backward. This time we have to avert dividing by zero:
Code:
var z_slip = 0
if not z_vel == 0:  z_slip = (spin * radius - z_vel) / abs(z_vel)
"Spin" must be tracked manually -- in fact, doing so is more useful than using a built-in angular velocity value on a physics object -- and it is modified elsewhere. See section IV below for calculating it.

Simplified Pacejka
Now it's time for the formula. You can actually get a half-decent approximation by skipping most of the parameters. The formula goes like this in English:
Force = Load * Peak * sin(Shape * arctan(Stiffness * Slip - Curvature * (Stiffness * Slip - arctan(Stiffness * Slip))))

...where "load" is the acting weight on the tire from the car (our "y_force" from part one), in Newtons, giving a force in Newtons (with the lateral slip angle in radians).

This is where it becomes handy to plot the formula to a graph (and where things start to get complicated). I used a nifty graphing calculator available on Desmos.com. (There, I've used the standard coefficient labels because of how the calculator works.)

Peak (D) = Simply determines the peak of the curve; comparable to coefficient of friction, I think? (example: 1)
Shape (C) = Affects the shape of the curve in the middle, after it has passed the peak (sources say ~1.35 for lateral force, ~1.65 for longitudinal force)
Stiffness (B) = Affects the slope of the curve around the origin (example: 10)
Curvature (E) = Affects the height of the tail of the curve, together with "shape" (example: 0, going negative from there)

If you haven't seen this curve before, it is a reasonably accurate approximation of the characteristics of a tire. As slip increases, there is an amount of slip that will provide the most force. To us drivers, generally speaking, if the curve is pointy and peaky, it will make for a "twitchy" tire that suddenly breaks away. If it is round and broad, the tire will be more forgiving. The lower the tail, the less traction you have once you've lost grip. The higher the peak, the more grip you get...at the peak.

I used Godot's "export" variables to make sliders for the parameters that can be modified in the Godot editor window while testing ("Always on top" in the window settings for rendering is nice). Something like this -- I honestly don't know if it is better to have separated values for all four coefficients:
Code:
export(float, 0, 2, 0.05) var peak = 1
export(float, 1, 2.5, 0.05) var x_shape = 1.35
export(float, 1, 2.5, 0.05) var z_shape = 1.65
export(float, 0, 20, 0.1) var stiff = 10
export(float, -10, 0, 0.05) var curve = 0

Then I created a method just for the formula:
Code:
func pacejka(slip, t_shape):
    return y_force * peak * sin(t_shape * atan(stiff * slip - curve * (stiff * slip - atan(stiff * slip))))
I have to use "t_shape" instead of just "shape" ('t' for tire) because SpringArm already has a "shape" property.

Now there's enough in place to apply some force via the tires. I figure it makes sense to implement the lateral force first -- add it where we applied "y_force" before. (Note: this example will use the X basis; I'll show how I rotated the surface normal when I bring things around to the brush formula, because I'm drawing from different stages of progress and don't want to mix up +/-):
Code:
x_force = pacejka(x_slip, x_shape)
z_force = pacejka(z_slip, z_shape)

if $RayCast.is_colliding():  # Check helps eliminate force spikes when tumbling
   car.add_force(global_transform.basis.x * x_force, contact)
   car.add_force(normal * y_force, contact)
   car.add_force(global_transform.basis.x * z_force, contact) ## See note in post below
This should be enough to shove the car around and let grip align it with its wheels (if the car spins like a top, chuck a negative sign in there). Without longitudinal force, it will basically act like it is always freewheeling. Otherwise, it will act like the brakes are all locked (so far). There's more to do before the longitudinal force can be properly implemented. That was one hard part to figure out, and this post is long enough.


In the above when i play it doesnt spin like a top but it moves around which i suppose it shouldnt. i mean that it gets and lateral force somehow. Could you please check my code if possible?

(Im using godot 4.1.1)
 
Back