Car Physics Simulation


Recentely i worked in a car simulation program that aimed to gather information from a real device and replay it (with some customs overlays) in an Virtual 3D environment.
The biggest problem i got was finding properly references and learning resources. (surprisely, is hard to find good material about this subject =P).
In this post i will show some valuable tips that helped me in this quest !

Car Simulation References:

Marco Monster (Best Start Point) Tutorial
VDrift Opensource (Good Reference) Driving Simulation
Book: Race Car Vehicle Dynamics (Milliken & Milliken)
Book: Automotive – Race Car Aerodynamics – Designing for Speed
Book: Theory of Ground Vehicles

In the rest of this post i will put some relevant code of our early prototypes.
The following code shows how to create a realistic linear motor for your car. It handles acceleration, braking and gearing. (Most of the ideas was takes from Marco Monster Tutorial)
Car Engine Simulation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace PloobsProjectTemplate.TemplateScreens
{
    public class CarEngine
    {
            public CarEngine()
            {
                wheelLength = (wheelRadius * 2 * Math.PI);//meters
            }

            private double wheelLength;

            //== CONSTANT ======================================
            public double Cdrag = 0.0257;
            public double Crr = 0.03;
            public double Gravity = 9.8;
            public double Area = 1.9;
            public double AirDensity = 1;
            public double EngineBraking = 1.54;            

            //== CAR SPEC ======================================
            public double Mass = 1200;//kg
            public double wheelRadius = 0.34;//meters            
            public double transmissionEfficiency = 0.8;
            public double[] gearRatios = new double[] {2.90, 2.0, 1.2, 1.05, 0.8, 0.44};
            public double differentialRatio = 7.42;
            public int currentGear= 0;
            public double Cbraking = 20000;

            public double torqueStart = 395;
            public double torqueMax = 475;
            public double torqueEnd = 390;
            public double rpmMin = 1000;
            public double rpmMax = 4400;
            public double redLine = 6000;
		
            //== VALUES ========================================
            public double kmh = 0;
            public double rpsWheel = 0;
            public double rpmWheel = 0;
            public double rpmEngine = 0;
		
            //== FORCE VECTORS =================================
            public double Ftraction = 0;            
            public double Flong = 0;
            public double Fdrag = 0;
            public double Frr = 0;
            public double Febrake = 0;

            public double  Fdrive = 0;
            public double Fbrake = 0;
            public double Tdrive = 0;
            public double maxTorque = 0;

            //== OTHER =========================================
            public double acceleration = 0;
            public double velocity = 0;
            public double speed = 0;
            public double deltaTime =  1f / 60f;//framerate of engine. 
            public double positionX = 0; //position of the car (not used).		
            public double degreeOfRotationPerFrame = 0;//car wheel rotation per frame (degrees)
            public double degreeOfRotationPerSecond = 0;//car wheel rotation per second (degrees)

        
            //=========================================================================
            // Handling
            //=========================================================================
            
            public void accelerate(float amount)
            {                
	            Ftraction = MathHelper.SmoothStep(0,1,amount);	            
            }

            public void brake(float amount)
            {
                Fbrake = -MathHelper.SmoothStep(0, 1, amount);
            }

            //=============================================================================
            //  POW 3
            //==============================================================================
            private double pow3(double x) {
	            return (x * x * x);
            }

            //=============================================================================
            //  EASE IN
            //==============================================================================
            public double easeIn(double t, double b, double c)
            {
	            return c * pow3( t ) + b;
            }

            //=============================================================================
            //  EASE OUT
            //==============================================================================
            public double  easeOut(double t, double b, double c) {
  	            return c * ( pow3( t-1 ) + 1) + b;
            }

            //========================================================================
            // GET TORQUE CURVE
            //=========================================================================
            public double getTorqueCurve(double rpm)
            {
	            if (rpm <= rpmMin) {
		            rpm = rpmMin;
	            }
	            if (rpm <= rpmMax) {
		            return Math.Max(0, easeOut((rpm-rpmMin) * (1f/rpmMax), torqueStart, (torqueMax-torqueStart)));
	            }else {
		            return Math.Max(0, easeIn((rpm-rpmMax) * (1f/(redLine-rpmMax)), torqueMax, -(torqueMax-torqueEnd)));
	            }
            }

        //=========================================================================
        // UPDATE
        //=========================================================================
        public void update(double deltaTime , float angle = 0){

            double peso = Gravity * Mass;
            double wv = peso * Math.Cos(angle);
            double wh = peso * Math.Sin(angle) * Math.Sign(angle);

            this.deltaTime = deltaTime;
	        maxTorque = getTorqueCurve( getRpmEngine() );
	        Tdrive =  maxTorque * gearRatios[currentGear] * differentialRatio * transmissionEfficiency;
	        Fdrive = Tdrive / wheelRadius;

            double Tbrake = EngineBraking * getRpmEngine() / 60f;
            Febrake = -Tbrake / wheelRadius;

	        speed = Math.Sqrt(velocity * velocity);
            Fdrag = -0.5f * Cdrag * AirDensity * Area * (velocity * speed);            
            Frr = -(Crr * wv);

            Flong = Febrake * (1 - Ftraction) + Cbraking * Fbrake + Fdrive * Ftraction + Fdrag + Frr + wh;
            
            acceleration = Flong / Mass;

	        velocity = velocity + (deltaTime * acceleration);

            if (velocity < 0)
                velocity = 0;

	        positionX = positionX + (deltaTime * velocity);           
            
        }

        
        //=========================================================================
        // GET RPM WHEEL
        //=========================================================================
        public double getRpmWheel() 
        {
	        degreeOfRotationPerFrame = ((velocity * deltaTime) / wheelLength) * 360f;
	        degreeOfRotationPerSecond = degreeOfRotationPerFrame * 30;
			
	        rpsWheel = degreeOfRotationPerSecond / 360f;
	        kmh = ((rpsWheel * wheelLength) * 3600f) / 1000f;
			
	        rpmWheel = rpsWheel * 60f;
	        return rpmWheel;
        }

        //=========================================================================
        // GET RPM ENGINE
        //=========================================================================
        public double getRpmEngine()
        {
	        rpmEngine = getRpmWheel() * gearRatios[currentGear] * differentialRatio;
	        return rpmEngine;
        }
    }
}

Experiment changing some physical parameters of the car !

The following code shows how to simulate the way car make curves in high speed (Most of the ideas was takes from Marco Monster Tutorial)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using PloobsEngine.SceneControl;
using PloobsEngine.Engine;
using PloobsEngine.Physics;
using Microsoft.Xna.Framework.Input;
using PloobsEngine.Modelo;
using PloobsEngine.Physics.Bepu;
using PloobsEngine.Material;
using PloobsEngine.Cameras;

namespace PloobsProjectTemplate.TemplateScreens
{
    public class CARTYPE
    {
        public float wheelbase;		// wheelbase in m
        public float b;				// in m, distance from CG to front axle
        public float c;				// in m, idem to rear axle
        public float h;				// in m, height of CM from ground
        public float mass;			// in kg
        public float inertia;		// in kg.m
        public float length, width;
        public float wheellength, wheelwidth;
    }

    public class CAR
    {
        public CARTYPE cartype;			// pointer to static car data

        public Vector2 position_wc;		// position of car centre in world coordinates
        public Vector2 velocity_wc;		    // velocity vector of car in world coordinates

        public float angle;				// angle of car body orientation (in rads)
        public float angularvelocity;

        public float steerangle;			// angle of steering (input)
        public float throttle;			// amount of throttle (input)
        public float brake;				// amount of braking (input)
    }

    public class CarScreen : IScene
    {

        float DELTA_T = 0.01f;		/* time between integration steps in physics modelling */
        float INPUT_DELTA_T = 0.1f;			/* delay between keyboard polls */

        /* Globals
         */

        CAR car;
        CARTYPE cartype;
        Vector2 screen_pos;
        float scale;
        volatile int ticks = 1;			// ticks of DELTA_T second
        volatile int iticks = 1;			// ticks of INPUT_DELTA_T second

        /* Lots of globals, so their value may be printed on screen
         * normally most of these variables should be private to the physics function.
         */

        Vector2 velocity;
        Vector2 acceleration_wc;
        double rot_angle;
        double sideslip;
        double slipanglefront;
        double slipanglerear;
        Vector2 force;
        int rear_slip;
        int front_slip = 1;
        Vector2 resistance;
        Vector2 acceleration;
        double torque;
        double angular_acceleration;
        double sn, cs;
        double yawspeed;
        double weight;
        Vector2 ftraction;
        Vector2 flatf, flatr;

        /*
 * Physics module
 */
        public void init_cartypes()
        {
            cartype = new CARTYPE();
            cartype.b = 1.0f;					// m							
            cartype.c = 1.0f;					// m
            cartype.wheelbase = cartype.b + cartype.c;
            cartype.h = 1.0f;					// m
            cartype.mass = 1500;				// kg			
            cartype.inertia = 1500;			// kg.m			
            cartype.width = 1.5f;				// m
            cartype.length = 3.0f;				// m, must be > wheelbase
            cartype.wheellength = 0.7f;
            cartype.wheelwidth = 0.3f;
        }

        public void init_car()
        {
            car = new CAR();
            car.cartype = cartype;

            car.position_wc.X = 0;
            car.position_wc.Y = 0;
            car.velocity_wc.X = 0;
            car.velocity_wc.Y = 0;

            car.angle = 0;
            car.angularvelocity = 0;

            car.steerangle = 0;
            car.throttle = 0;
            car.brake = 0;
        }

        // These constants are arbitrary values, not realistic ones.

        float DRAG = 5.0f;		 		/* factor for air resistance (drag) 	*/
        float RESISTANCE = 30.0f;			/* factor for rolling resistance */
        float CA_R = -5.20f;			/* cornering stiffness */
        float CA_F = -5.0f;			/* cornering stiffness */
        float MAX_GRIP = 2.0f;				/* maximum (normalised) friction force, =diameter of friction circle */

        public void do_physics(CAR car, float delta_t)
        {
            sn = Math.Sin(car.angle);
            cs = Math.Cos(car.angle);

            if (car.steerangle != 0.0f)
            {
                int breakme = 1;
            }

            // SAE convention: x is to the front of the car, y is to the right, z is down

            // bangz: Velocity of Car. Vlat and Vlong
            // transform velocity in world reference frame to velocity in car reference frame
            // 2D rotation of the velocity vector by car orientation ...
            velocity.X = (float)(cs * car.velocity_wc.Y + sn * car.velocity_wc.X);
            velocity.Y = (float)(-sn * car.velocity_wc.Y + cs * car.velocity_wc.X);

            // Lateral force on wheels
            //	
            // Resulting velocity of the wheels as result of the yaw rate of the car body
            // v = yawrate * r where r is distance of wheel to CG (approx. half wheel base)
            // yawrate (ang.velocity) must be in rad/s
            //
            yawspeed = car.cartype.wheelbase * 0.5 * car.angularvelocity;

            //bangz: velocity.x = fVLong_, velocity.y = fVLat_
            if (velocity.X == 0)		// TODO: fix singularity
                rot_angle = 0;
            else
                rot_angle = Math.Atan2(yawspeed, velocity.X);

            // Calculate the side slip angle of the car (a.k.a. beta)
            if (velocity.X == 0)		// TODO: fix singularity
                sideslip = 0;
            else
                sideslip = Math.Atan2(velocity.Y, velocity.X);

            // Calculate slip angles for front and rear wheels (a.k.a. alpha)
            slipanglefront = sideslip + rot_angle - car.steerangle;
            slipanglerear = sideslip - rot_angle;

            // weight per axle = half car mass times 1G (=9.8m/s^2) 
            weight = car.cartype.mass * 9.8f * 0.5f;

            // lateral force on front wheels = (Ca * slip angle) capped to friction circle * load
            flatf.X = 0;
            flatf.Y = (float)(CA_F * slipanglefront);
            flatf.Y = Math.Min(MAX_GRIP, flatf.Y);
            flatf.Y = Math.Max(-MAX_GRIP, flatf.Y);
            flatf.Y *= (float)weight;
            if (front_slip == 1)
                flatf.Y *= 0.5f;

            // lateral force on rear wheels
            flatr.X = 0;
            flatr.Y = (float)(CA_R * slipanglerear);
            flatr.Y = Math.Min(MAX_GRIP, flatr.Y);
            flatr.Y = Math.Max(-MAX_GRIP, flatr.Y);
            flatr.Y *= (float)weight;
            if (rear_slip == 1)
                flatr.Y *= 0.5f;

            // longtitudinal force on rear wheels - very simple traction model
            ftraction.X = 100 * (car.throttle - car.brake * Math.Sign(velocity.X));
            ftraction.Y = 0;
            if (rear_slip == 1)
                ftraction.X *= 0.5f;

            // Forces and torque on body

            // drag and rolling resistance
            resistance.X = -(RESISTANCE * velocity.X + DRAG * velocity.X * Math.Abs(velocity.X));
            resistance.Y = -(RESISTANCE * velocity.Y + DRAG * velocity.Y * Math.Abs(velocity.Y));

            // sum forces
            force.X = (float)(ftraction.X + Math.Sin(car.steerangle) * flatf.X + flatr.X + resistance.X);
            force.Y = (float)(ftraction.Y + Math.Cos(car.steerangle) * flatf.Y + flatr.Y + resistance.Y);

            // torque on body from lateral forces
            torque = car.cartype.b * flatf.Y - car.cartype.c * flatr.Y;

            // Acceleration

            // Newton F = m.a, therefore a = F/m
            acceleration.X = force.X / car.cartype.mass;
            acceleration.Y = force.Y / car.cartype.mass;

            angular_acceleration = torque / car.cartype.inertia;

            // Velocity and position

            // transform acceleration from car reference frame to world reference frame
            acceleration_wc.X = (float)(cs * acceleration.Y + sn * acceleration.X);
            acceleration_wc.Y = (float)(-sn * acceleration.Y + cs * acceleration.X);

            // velocity is integrated acceleration
            //
            car.velocity_wc.X += delta_t * acceleration_wc.X;
            car.velocity_wc.Y += delta_t * acceleration_wc.Y;

            // position is integrated velocity
            //
            car.position_wc.X += delta_t * car.velocity_wc.X;
            car.position_wc.Y += delta_t * car.velocity_wc.Y;

            // Angular velocity and heading

            // integrate angular acceleration to get angular velocity
            //
            car.angularvelocity += (float)(delta_t * angular_acceleration);

            // integrate angular velocity to get angular orientation
            //
            car.angle += delta_t * car.angularvelocity;

            //F = total of forces acting on this point
            //T = Time step to update over
            //X0 is the previous position, X1 is the current position
            //XT = X1
            //X1 += (X1-X0) + F/M*T*T
            //X0 = XT

        }

        #region Input
        /*
 * End of Physics module
 */
        ///  /// Sets the world and render technich. /// 
        ///The render tech.
        ///The world.
        protected override void SetWorldAndRenderTechnich(out IRenderTechnic renderTech, out IWorld world)
        {
            ///create the IWorld
            world = new IWorld(new BepuPhysicWorld(-0.97f, true, 1), new SimpleCuller());

            ///Create the deferred technich
            ForwardRenderTecnichDescription desc = new ForwardRenderTecnichDescription();
            desc.BackGroundColor = Color.AliceBlue;
            renderTech = new ForwardRenderTecnich(desc);
        }

        ///  /// Load content for the screen. /// 
        ///
        ///
        ///
        protected override void LoadContent(GraphicInfo GraphicInfo, GraphicFactory factory, IContentManager contentManager)
        {
            base.LoadContent(GraphicInfo, factory, contentManager);

            this.init_cartypes();
            this.init_car();

            {
                SimpleModel simpleModel = new SimpleModel(factory, "Model//block");
                simpleModel.SetTexture(factory.CreateTexture2DColor(1, 1, Color.Red), TextureType.DIFFUSE);
                BoxObject tmesh = new BoxObject(new Vector3(0), 1, 1, 1, 10, new Vector3(1000, 1, 1000), Matrix.Identity, MaterialDescription.DefaultBepuMaterial());
                tmesh.isMotionLess = true;
                ForwardXNABasicShader shader = new ForwardXNABasicShader(ForwardXNABasicShaderDescription.Default());
                ForwardMaterial fmaterial = new ForwardMaterial(shader);
                IObject obj = new IObject(fmaterial, simpleModel, tmesh);
                this.World.AddObject(obj);
            }

            {
                SimpleModel simpleModel = new SimpleModel(factory, "Model//block");
                simpleModel.SetTexture(factory.CreateTexture2DColor(1, 1, Color.Green), TextureType.DIFFUSE);
                GhostObject tmesh = new GhostObject(new Vector3(0, 20, 0), Matrix.Identity, Vector3.One);                
                tmesh.isMotionLess = true;
                ForwardXNABasicShader shader = new ForwardXNABasicShader(ForwardXNABasicShaderDescription.Default());
                ForwardMaterial fmaterial = new ForwardMaterial(shader);
                UserObject UserObject = new UserObject(fmaterial,simpleModel,tmesh,car);
                UserObject.OnUserUpdate += new Action<UserObject>(UserObject_OnUserUpdate);
                this.World.AddObject(UserObject);
            }

            this.World.CameraManager.AddCamera(new CameraFirstPerson(GraphicInfo));

        }

        void UserObject_OnUserUpdate(UserObject obj)
        {
            obj.PhysicObject.Position = new Vector3(obj.UserData.position_wc.X, 20, obj.UserData.position_wc.Y);
            obj.PhysicObject.Rotation = Matrix.CreateRotationY(obj.UserData.angle);
        }

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            do_physics(car, DELTA_T);

            KeyboardState KeyboardState =  Keyboard.GetState();
            if (KeyboardState.IsKeyDown(Keys.Y))	// throttle up
            {
                if (car.throttle < 100)
                    car.throttle += 10;
            }
            if (KeyboardState.IsKeyDown(Keys.H)) // throttle down
            {
                if (car.throttle >= 10)
                    car.throttle -= 10;
            }

            if (KeyboardState.IsKeyDown(Keys.Space))	// brake
            {
                car.brake = 100;
                car.throttle = 0;
            }
            else
                car.brake = 0;

            // Steering 
            //
            if (KeyboardState.IsKeyDown(Keys.J))
            {
                if (car.steerangle > - MathHelper.Pi / 4.0f)
                    car.steerangle -= MathHelper.Pi / 32.0f;
            }
            else if (KeyboardState.IsKeyDown(Keys.G))
            {
                if (car.steerangle < MathHelper.Pi / 4.0f)
                    car.steerangle += MathHelper.Pi / 32.0f;
            }

        }


    }
}

Both prototypes used our PloobsEngine.
The prototypes just show the basics, you can easily add lots of others effects like wight transfer, suspension, aerodinamics wings effects =P ...
Lots of Physic Engines prefer to simulate Wheels individually using Raycasts, that normally is much more stable than the used aproach.

  1. #1 by BrellTrig on 22 de junho de 2017 - 2:14 pm

    personal loans for bad credit – https://badcreditjkkl.com/loans bad credit [url=https://badcreditjkkl.com/]bad credit loan payday loans payday lender bad credit loan[/url] ’

  2. #2 by hier klicken um mehr lesen on 22 de junho de 2017 - 2:15 pm

    Neben Ketten, Kopfschmuck, Ringen und Armbänder gibt es auch die Bauchketten.

  3. #3 by Travel and Leisure on 22 de junho de 2017 - 3:04 pm

    You are a very intelligent individual!

  4. #4 by Legal Resources on 22 de junho de 2017 - 3:52 pm

    Wow! This can be one particular of the most beneficial blogs We’ve ever arrive across on this subject. Basically Magnificent. I am also a specialist in this topic therefore I can understand your hard work.

  5. #5 by Kimberley on 22 de junho de 2017 - 4:28 pm

    It’s an awesome post designed for all the internet users; they will take advantage from it I am sure.

  6. #6 by you could look here on 22 de junho de 2017 - 4:46 pm

    I just want to mention I am all new to weblog and seriously savored this web site. More than likely I’m want to bookmark your website . You actually have fantastic posts. Kudos for sharing your web-site.

  7. #7 by af1 on 22 de junho de 2017 - 5:03 pm

    I really wanted to write a comment to say thanks to you for these fantastic advice you are writing on this website. My rather long internet look up has now been paid with incredibly good content to exchange with my partners. I ‘d state that that most of us site visitors actually are undoubtedly endowed to dwell in a wonderful place with very many lovely professionals with useful things. I feel extremely grateful to have come across your web site and look forward to many more pleasurable moments reading here. Thanks once again for everything.

  8. #8 by pop over to this website on 22 de junho de 2017 - 5:09 pm

    I just want to mention I am just very new to blogging and site-building and certainly loved this web blog. Likely I’m going to bookmark your blog post . You certainly have great posts. Thanks for sharing with us your web site.

  9. #9 by Scissor lift repair miami on 22 de junho de 2017 - 6:47 pm

    Here is an excellent Blog You may Come across Intriguing that we Encourage You

  10. #10 by Education and Training on 22 de junho de 2017 - 7:11 pm

    You are my aspiration , I own few blogs and often run out from to post .I think this website has got some really great information for everyone. “The expert at anything was once a beginner.” by Hayes.

  11. #11 by katie on 22 de junho de 2017 - 7:16 pm

    why are diet pills bad for you

1 575 576 577
(não será publicado)