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 http://catab.se/wp-content/plugins/symple/386.html on 27 de abril de 2017 - 10:56 pm

    This particular headset had been because beautiful just as inside picture. That it appeared immediately. I’d advise utilizing a towel through each and every layer while you hit it away w / the steam iron. It does not steam out through merely a steamer. Ones iron ended up being needed. It is very delicate, when you do not trust your self with the towel and also steam iron, well bring things up to a pro. Striking seem.

  2. #2 by http://news.moncleroutletonline.pw/moncleroutletonlinepw/408.asp on 27 de abril de 2017 - 11:07 pm

    I have purchased this one brand out of bracelet some occasions. Every single one is ultra cute, made very well, does not tarnish and also important depending on that definitely one you purchase additionally that a person offer things and.

  3. #3 by http://news.ordersoho.com/ordersoho/999.asp on 27 de abril de 2017 - 11:08 pm

    It item had been at that ideal amount I by no means consideration some sort of top quality is hence exceptional. It is awesome. This mother is going to really like things regarding Christmas day after she opens up gifts therefore looks as though I devoted alot more, however cost was exclusively awesome!!

  4. #4 by Google on 27 de abril de 2017 - 11:25 pm

    Wonderful story, reckoned we could combine a handful of unrelated data, nonetheless definitely really worth taking a search, whoa did 1 understand about Mid East has got extra problerms too.

  5. #5 by gucci outlet online on 28 de abril de 2017 - 4:07 am

    Awesome Post! I am so glad I found this. This is exactly what I was looking for. Thanks for Sharing. I’ll definately visit again!
    gucci outlet online http://www.lticonstruction.com

  6. #6 by http://news.word-vorlagen.xyz/wordvorlagenxyz/308.asp on 28 de abril de 2017 - 4:21 am

    I’ve bought your brand to bracelet some instances. Every single a person is extremely pretty, established well, does not tarnish and also meaningful depending on which single you buy and that you promote this at.

  7. #7 by http://news.christianlouboutinreplica.win/christianlouboutinreplicawin/419.asp on 28 de abril de 2017 - 4:21 am

    I have bought this one brand to bracelet a number of period. Every single one is extremely pretty, made very well, cannot tarnish as well as important depending on which definitely one you buy then just who we promote things and.

  8. #8 by http://news.cartierlovebraceletreplica.xyz/cartierlovebraceletreplicaxyz/205.asp on 28 de abril de 2017 - 4:22 am

    I’ve purchased the brand name out of bracelet various occasions. Every single a person is ultra attractive, has made well, does not tarnish and also important based on which kind of definitely one you purchase and also that one offer things and.

  9. #9 by http://skoldnora.se/wp-content/themes/page/327.html on 28 de abril de 2017 - 4:38 am

    This particular headset is as spectacular while into the visualize. Things appeared immediately. I’d encourage choosing a towel during every single layer as you push information technology over w / a vapor iron. It doesn’t vapor away and only a steamer. That iron is appropriate. It is very fragile, so if you cannot trust your self using the towel to steam iron, subsequently just take it up to a expert. Perfect seem.

  10. #10 by http://www.deshvani.in/tempxml/1.html on 28 de abril de 2017 - 4:38 am

    Our headset had been like breathtaking because inside picture. It came immediately. I’d advise choosing a towel done every layer as you hit they out w / any steam iron. It doesn’t vapor over along with merely a steamer. That iron is appropriate. It is very sensitive, so if you you should not trust yourself with the towel as well as steam iron, next just take that it to a professional. Pretty noise.

  11. #11 by w88 link on 28 de abril de 2017 - 4:55 am

    I have read several good stuff here. Definitely worth bookmarking for revisiting. I surprise how much effort you put to create such a excellent informative website.

  12. #13 by generic viagra on 28 de abril de 2017 - 5:50 am

    Obama had more than one issued social security number, an altered draft registration, numerous places of residences that he never lived at, his Harvard & Columbia education was paid for by the Saudi Prince! http://www.westernjournalism.com —IMPEACH THIS FRAUDULENT, TREASONOUS, LYING, DECEITFUL PRESIDENT! WE HAVE HAD ENOUGH OF HIM AND HIS THUGS IN OFFICE!

  13. #14 by cheap viagra on 28 de abril de 2017 - 6:04 am

    Went to your MYspace page. Not only are you talented with your hands but also with your voice. Wow…what a voice! Good luck!Aloha from Hawaii

  14. #15 by w88 link on 28 de abril de 2017 - 6:08 am

    Great post. I was checking continuously this blog and I’m impressed! Very helpful information specifically the last part :) I care for such info a lot. I was seeking this certain info for a very long time. Thank you and good luck.

1 530 531 532
(não será publicado)