Issue with vehicle suspension when applying angular forces

Godot Version

4.2.stable.mono

Question

I am creating a script to do custom vehicle suspension. The script applies an upward force at each wheel position according to the offset of the collision point to the rest distance, the current velocity of the wheel, the spring strength, and the damping strength.

The issue is that when the vehicle is rotated to some degree (I do this by applying an angular force), the forces applied seem to oscillate as if it looses balance.

Godot_v4.2-stable_mono_win64_YoP35OSfrf

I’m not sure what the problem is here. I am unfamiliar with Godot’s physics engine and pretty new to Godot in general.

Vehicle.cs
using Godot;
using System;

namespace Vehicles
{
	public partial class Vehicle : RigidBody3D
	{
		Wheel fl; public Wheel FL { get => fl; }
		Wheel fr; public Wheel FR { get => fr; }
		Wheel rl; public Wheel RL { get => rl; }
		Wheel rr; public Wheel RR { get => rr; }


		[Export] float wheel_base;          // front wheels distance to rear wheels
		[Export] float track_width;         // left wheels distance to right wheels
		[Export] float max_torque = 100;
		[Export] Curve torqueCurve;

		// Suspension variables
		[Export] float restdistance;
		public float RestDistance { get => restdistance; }
		[Export] float spring_strength;
		public float SpringStrength { get => spring_strength; }
		[Export] float damping_strength;
		public float DampingStrength { get => damping_strength; }

		public override void _Ready()
		{
			fl = (Wheel)GetNode("FrontLeftWheel");
			fr = (Wheel)GetNode("FrontRightWheel");
			rl = (Wheel)GetNode("RearLeftWheel");
			rr = (Wheel)GetNode("RearRightWheel");

			SetRestDistance(restdistance);
			SetWheelBase(wheel_base);
			SetTrackWidth(track_width);
		}

		public override void _PhysicsProcess(double delta)
		{

			if (fl.RayCast3D.IsColliding())
			{
				AddSuspensionForce(fl, (float)delta);
			}
			if (fr.RayCast3D.IsColliding())
			{
				AddSuspensionForce(fr, (float)delta);
			}
			if (rl.RayCast3D.IsColliding())
			{
				AddSuspensionForce(rl, (float)delta);
			}
			if (rr.RayCast3D.IsColliding())
			{
				AddSuspensionForce(rr, (float)delta);
			}
		}

		void AddSuspensionForce(Wheel wheel, float delta)
		{
			float offset = restdistance - wheel.GlobalPosition.DistanceTo(wheel.RayCast3D.GetCollisionPoint());

			wheel.TireVel = GetWheelVelocity(wheel, delta);
			wheel.SpringVel = GlobalBasis.Y.Dot(wheel.TireVel);

			wheel.SuspensionForce = (offset * spring_strength) - (wheel.SpringVel * damping_strength);

			ApplyForce(GlobalBasis.Y * wheel.SuspensionForce, wheel.Position);
		}

		Vector3 GetWheelVelocity(Wheel wheel, float delta)
		{
			Vector3 velocity = (wheel.GlobalPosition - wheel.PreviousPosition) / delta;
			wheel.PreviousPosition = wheel.GlobalPosition;
			return velocity;
		}

		public void SetWheelBase(float newWheelBase)
		{
			wheel_base = newWheelBase;
			fl.Position = new Vector3(fl.Position.X, fl.Position.Y, wheel_base / 2);
			fr.Position = new Vector3(fr.Position.X, fr.Position.Y, wheel_base / 2);
			rl.Position = new Vector3(rl.Position.X, rl.Position.Y, -(wheel_base / 2));
			rr.Position = new Vector3(rr.Position.X, rr.Position.Y, -(wheel_base / 2));
		}

		public void SetTrackWidth(float newTrackWidth)
		{
			track_width = newTrackWidth;
			fl.Position = new Vector3(-(track_width / 2), fl.Position.Y, fl.Position.Z);
			fr.Position = new Vector3(track_width / 2, fr.Position.Y, fr.Position.Z);
			rl.Position = new Vector3(-(track_width / 2), rl.Position.Y, rl.Position.Z);
			rr.Position = new Vector3(track_width / 2, rr.Position.Y, rr.Position.Z);
		}

		public void SetRestDistance(float newRestDistance)
		{
			restdistance = newRestDistance;
			fl.RayCast3D.TargetPosition = new Vector3(0, -restdistance, 0);
			fr.RayCast3D.TargetPosition = new Vector3(0, -restdistance, 0);
			rl.RayCast3D.TargetPosition = new Vector3(0, -restdistance, 0);
			rr.RayCast3D.TargetPosition = new Vector3(0, -restdistance, 0);
		}
	}
}

Godot Physics are not in a good spot right now it commonly has issues and some of the joint physics aren’t even fully implemented, you would most likely have to dig into the source code to see for yourself whats going on. Many have opted to using the Jolt physics engine add-on.

Watching your video it seems like you get some pretty large force spikes, I feel like you should make a physical limit to the forces quantity you can apply. A shock spring is only like a foot long and can only travel so far, and simulation wise nothing is holding it back.

1 Like

Thanks for the reply! I tried capping the suspension force but it still looses balance and tips over. I will try switching to Jolt physics since its apparently much better anyways. I will provide an update on that soon!

I switched to Jolt physics but the issue persists. I also changed the weight to be closer to an actual car.

When it rotates, it’s perfectly balanced and then at a certain point it just falls over.

Godot_v4.2-stable_mono_win64_j8uAxOxo3Z

You can totally do this in Godot. I attempted this same thing a few months back and had a similar issue. I even tried switching to Jolt as well and had the same problem.

Here’s a little video of the final result:
https://www.reddit.com/r/godot/comments/18eqbkn/played_around_with_raycastbased_vehicle_physics/

I wanna say the problem ended up being where I was applying the forces (local vs global). In your case maybe this could work:

ApplyForce(GlobalBasis.Y * wheel.SuspensionForce, wheel.GlobalPosition - GlobalPosition);
2 Likes

Thank you! this seemed to have fixed the issue. Now when I rotate it stays stable and doesn’t flip over.

The ApplyForce function takes in an offset from the global position. I thought using local position would work as an offset however when rotated the direction in local space stays the same because of course it does!

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.