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 ブルガリ コピー 時計 on 22 de julho de 2017 - 11:46 am

    http://www.kunangkunangguesthouse.com/スーパーコピー腕時&#35336;
    [url=http://dasilva-stones.com/]ブルガリ コピー 時計[/url]

  2. #2 by Geico motorcycle quote on 22 de julho de 2017 - 1:33 pm

    Wow, fantastic blog layout! How long have you been blogging for? you made blogging look easy. The overall look of your web site is fantastic, as well as the content!

  3. #3 by Arlenewah on 22 de julho de 2017 - 2:23 pm

    Revolutional update of SEO/SMM software “XRumer 16.0 + XEvil”:
    captchas solution of Google, Facebook, Bing, Hotmail, SolveMedia, Yandex,
    and more than 8400 another size-types of captchas,
    with highest precision (80..100%) and highest speed (100 img per second).
    You can connect XEvil 3.0 to all most popular SEO/SMM programms: XRumer, GSA SER, ZennoPoster, Srapebox, Senuke, and more than 100 of other programms.

    Interested? You can find a lot of introducing videos about XEvil in YouTube.
    Good luck!

    XRumer20170721

  4. #4 by cartier love ring replica on 22 de julho de 2017 - 3:56 pm

    We have purchased your brand name out of bracelet some instances. Every a person is extremely sweet, made very well, cannot tarnish to significant depending on that a single you buy as well as which one provide things to.

  5. #5 by pocket mastubator on 22 de julho de 2017 - 4:39 pm

    Every when inside a although we select blogs that we read. Listed below are the most current internet sites that we opt for

  6. #6 by seo company on 22 de julho de 2017 - 9:57 pm

    I really advise to make use of GSA Search Engine Ranker
    for Rate 2 as well as Rate 3 in priority.

  7. #7 by Homeowner s insurance on 22 de julho de 2017 - 10:28 pm

    I got good info from your blog

  8. #8 by Myrtle Beach landscaping contractor on 22 de julho de 2017 - 10:44 pm

    Thank you for the good writeup. It in fact was a amusement account it.
    Look advanced to far added agreeable from you! By
    the way, how could we communicate?

  9. #9 by hermes bracelet replica on 23 de julho de 2017 - 3:35 am

    We have purchased that brand regarding bracelet a number of circumstances. Every single one is very attractive, done well, does not tarnish and significant depending on what definitely one you purchase and also whom a person render they and.

  10. #10 by replica christian louboutin sale on 23 de julho de 2017 - 3:36 am

    I’ve purchased this particular brand name out of bracelet various instances. Every one is extremely attractive, done well, does not tarnish as well as meaningful depending on which any you purchase and whom a person bring it to.

  11. #11 by PORN on 23 de julho de 2017 - 4:28 am

    Visit our new site for the best porn videos!

  12. #12 by correx on 23 de julho de 2017 - 6:45 am

    below you will come across the link to some sites that we assume you’ll want to visit

  13. #13 by correx on 23 de julho de 2017 - 8:00 am

    It is truly a nice and useful piece of info. I’m happy that
    you shared this useful information with us. Please stay us informed like this.
    Thanks for sharing.

  14. #14 by peppermintskin on 23 de julho de 2017 - 10:15 am

    I sorry for my English.Wow, marvelous blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your web site is great, as well as the content!

  15. #15 by http://www.ljusihus.se/gzreplica.aspx on 23 de julho de 2017 - 1:49 pm

    We have bought it brand of bracelet a few instances. Every one is super sweet, done well, cannot tarnish as well as meaningful depending on which an individual you purchase plus which shoppers give this in order to.

  16. #16 by Cartier Love Ring Replica on 23 de julho de 2017 - 1:49 pm

    This one system ended up being at such a great cost I never ever idea the particular premium would be and so exceptional. Its stunning. It mama is going to love this concerning Xmas early morning after she starts gifts and it seems like I spent way more, still rates had been exclusively ideal!!

  17. #17 by automotive products on 23 de julho de 2017 - 2:47 pm

    Hello. excellent job. I did not anticipate this. This is a impressive story. Thanks!

  18. #18 by Hector on 23 de julho de 2017 - 6:36 pm

    Have been taking little over a month.

1 596 597 598
(não será publicado)