Car physics with colliders on the wheels?

I have model cars that are pushed along routes using the physics. It works pretty well and provides real driving look instead of looking like on tracks, the cars meander some or can fall of the road. The whole vehicle is one big box collider. This means that sometimes the front edge snags on the terrain and the cars flip onto their nose which is silly. So I think I need to model the physics with the wheels as separate objects connected to the body. That way only they collide with the terrain. and I can apply the forces to the wheels. I am not sure how to accomplish this, and was hoping maybe there were examples. I see that other engines offer a wheel collider which would be really cool. But I think even box colliders on the wheels would behave more realistically.

While I don’t have a simple answer, I know that latest Kraft ( GitHub - BeRo1985/kraft: Kraft Physics Engine is an open source Object Pascal physics engine library that can be used in 3D games. ) features a demo of car with physics.

Background: Kraft is made by Benjamin ‘BeRo’ Rosseaux, it’s an independent physics engine that e.g. BeRo uses throughout his projects. We also use Kraft in Castle Game Engine – we hide it (to have simple API on top with CGE behaviors), so when using physics from CGE you do not see the Kraft API, but it’s there underneath.

The car physics demo is in “sandbox” subdirectory of Kraft. You need to get a few BeRo’s repositories and place them alongside to compile it:

git clone https://github.com/BeRo1985/kraft
git clone https://github.com/BeRo1985/pasdblstrutils
git clone https://github.com/BeRo1985/pasjson
git clone https://github.com/BeRo1985/pasmp
git clone https://github.com/BeRo1985/pucu

# Now you should be able to build from Lazarus kraft/sandbox/kraftsandbox.lpi

# Or build from command-line, if you wish:
cd kraft/sandbox/
lazbuild kraftsandbox.lpi

Once run, this is, from what I understand, something you’re interested in here: a car simulation using physics, where each wheel is a physical body and they are all connected using proper joints. You can move using AWSD.

I suspect not everything there may be possible in CGE. While CGE uses Kraft, we do not expose 100% of Kraft functionality – we only expose the things we consider most commonly needed, they will also be present in other game engines (as we may use PhysX in the future in CGE, as alternative to CGE), and also things which are clear how to use in Kraft (some Kraft pieces are not documented, and it is not always obvious how to use them :slight_smile: ). I also saw some specific “car physics” units there, so possibly this is more involved that just using “generic colliders and joints”.

Still, maybe exploring this will give you an idea how to design a similar thing using CGE. We have colliders and joints in CGE (though not all joints exposed in Kraft).

1 Like

I have the same problem.
I get a direction from the translation of the left wheel and the translation of the right wheel. But how can I make the SceneCar to rotate in this direction?

I mean the roll axis. The pitch axis is not the problem.

grafik

I use this:

function anglefromdelta2d( const delta : TVector2 ) : single;
 begin
   if abs( delta.x ) > 0 then
      Result := arctan2(delta.y,delta.x)
   else
       result := 0;
 end;

function CalcRotationVector( angle, rotate, camber: single ) : TVector4; overload;
 var q1, q2, q3, qc : TQuaternion;
     axis : TVector3;
 begin
   q1 := QuatFromAxisAngle(Vector3(0, 1, 0), rotate );
   q2 := QuatFromAxisAngle(Vector3(1, 0, 0), angle );
   q3 := QuatFromAxisAngle(Vector3(0, 0, 1), camber );
   qc := q1 * q2 * q3;
   qc.ToAxisAngle(Axis, angle);
   Result := Vector4( Axis.x, axis.y, axis.z, angle );
 end;

function CalcRotationVector( angle, rotate : single ) : TVector4; overload;
 var q1, q2, qc : TQuaternion;
     axis : TVector3;
 begin
   q1 := QuatFromAxisAngle(Vector3(0, 1, 0), rotate );
   q2 := QuatFromAxisAngle(Vector3(1, 0, 0), angle );
   qc := q1 * q2;
   qc.ToAxisAngle(Axis, angle);
   Result := Vector4( Axis.x, axis.y, axis.z, angle );
 end;

procedure rotatetomatchterrain( item : TCastleTransform;
                                heading : single;
                                terrainheight : THeightAboveTerrainEvent;
                                MatchCamber : boolean = true);
 var p1, p2 : TVector3;
     length, width, l2, w2 : single;
     angle, camber : single;
     newpos : TVector3;
     d2 : TVector2;
     sinh, cosh : single;
     delta : TVector2;
begin
   length := ( item.LocalBoundingBox.Max.z - item.localboundingBox.Min.z ) * item.Scale.z;
   width := ( item.LocalBoundingBox.Max.x - item.localboundingBox.Min.x ) * item.Scale.x;
   assert( length > 0 );
   l2 := length*0.5;
   w2 := width *0.5;
   newpos := item.translation;

   sincos(heading, sinh, cosh );
   d2 := Vector2(sinh * l2, cosh * l2 );
   p1 := vector3( newpos.x + d2.x, item.translation.y, newpos.z + d2.y ); { front position }
   p2 := vector3( newpos.x - d2.x, item.translation.y, newpos.z - d2.y );  { rear position }
   { determine front and back point and their heights on terrain }
    terrainheight( p1, p1.y, ht_road );
    terrainheight( p2, p2.y, ht_road );

    { calculate the angle forward/back to match the terrain}
    delta := Vector2( length, p2.y - p1.y );
    angle := anglefromdelta2d( delta );

    { calculate the camber left/right to match the terrain }
    if MatchCamber then
     begin
       sincos(heading + Pi/2, sinh, cosh );
       d2 := Vector2(sinh * w2, cosh * w2 );
       p1 := vector3( newpos.x + d2.x, item.translation.y, newpos.z + d2.y ); { left position }
       p2 := vector3( newpos.x - d2.x, item.translation.y, newpos.z - d2.y ); { right position }
       { determine left and right point and their heights on terrain }
       terrainheight( p1, p1.y, ht_road );             {right}
       terrainheight( p2, p2.y, ht_road );             {left}

       { calculate the camber left/right to match the terrain}
       delta := Vector2( width, p2.y - p1.y );
       camber := -anglefromdelta2d( delta );
       Item.Rotation := CalcRotationVector( angle, heading, camber );
     end
    else
       Item.Rotation := CalcRotationVector( angle, heading );
 end;

The terrainheight function is based on the one in wyrdforest, except mine adds an option to get the value from roads too. you can ignore that.
I use this with cars and animals. For the animals I set MatchCamber to false so they don’t angle sideways like cars do.

I also use the physics, and then reposition all the vehicles using this routine when I modify the terrain to dig for a road. Otherwise all the cars fall through the terrain when it rebuilds.

Thank you for this code. It was difficult to figure out how to adapt it to my program. But I think it was worth it.

1 Like

That looks good! Glad it helped.