diff --git a/Car simulation.slnx b/Car simulation.slnx
new file mode 100644
index 0000000..11cb5cd
--- /dev/null
+++ b/Car simulation.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Car simulation/Car simulation.csproj b/Car simulation/Car simulation.csproj
new file mode 100644
index 0000000..027dfcc
--- /dev/null
+++ b/Car simulation/Car simulation.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net10.0
+ Car_simulation
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Car simulation/Car.cs b/Car simulation/Car.cs
new file mode 100644
index 0000000..0ecd04e
--- /dev/null
+++ b/Car simulation/Car.cs
@@ -0,0 +1,221 @@
+using static SFML.Window.Mouse;
+
+namespace Car_simulation
+{
+ public class Car
+ {
+ public Vector2 Position = new Vector2(0, 0);
+ public Vector2 Velocity = new Vector2(0, 0);
+ public float Speed => Velocity.Length;
+
+ public float Mass = 1500f; // kg
+ public int WheelCount = 4;
+ public int DrivenWheels = 2;
+
+ public float ThrottleInput = 0f;
+ public float BrakeInput = 0f;
+ public float ClutchInput = 1f; // 0 = engaged, 1 = disengaged
+ public bool ForceClutch = false;
+ public float SteeringInput = 0f;
+
+ // Aerodynamics
+ private const float AirDensity = 1.225f;
+ public float DragCoefficient = 0.1f;
+ public float FrontalArea = 2.2f; // m²
+ public float RollingResistanceCoefficient = 0.015f;
+
+ // Components
+ public Engine Engine;
+ public Drivetrain Drivetrain;
+ public WheelSystem WheelSystem;
+
+ private EngineSound _engineSound;
+ private bool _audioEnabled = true;
+
+ public Car()
+ {
+ Engine = new Engine();
+ WheelSystem = new WheelSystem();
+ Drivetrain = new Drivetrain(Engine, WheelSystem);
+
+ // Initial setup
+ WheelSystem.WheelCount = WheelCount;
+ WheelSystem.DrivenWheels = DrivenWheels;
+
+ InitializeAudio();
+ }
+
+ private void InitializeAudio()
+ {
+ try
+ {
+ _engineSound = new EngineSound();
+ _engineSound.SetEngineState(Engine.IdleRPM, 0f);
+ _engineSound.StartSound();
+
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Audio initialization failed: {ex.Message}");
+ _audioEnabled = false;
+ }
+ }
+
+ public void Update(float deltaTime)
+ {
+ Engine.Throttle = ThrottleInput;
+ Drivetrain.ClutchEngagement = 1f - ClutchInput; // Convert: 0 input = 1 engagement
+
+ if (ForceClutch)
+ Drivetrain.ClutchEngagement = 0f;
+
+ float resistanceTorque = CalculateResistanceTorque();
+ WheelSystem.ResistanceTorque = resistanceTorque;
+
+ Drivetrain.Update(deltaTime);
+ WheelSystem.ApplyResistance(deltaTime);
+
+ float engineLoad = Drivetrain.CalculateEngineLoad(deltaTime);
+ Engine.Update(deltaTime, engineLoad);
+
+ UpdateVehicleMotion(deltaTime);
+ ApplyBraking(deltaTime);
+
+ if (_audioEnabled)
+ {
+ UpdateAudio();
+ }
+ }
+
+ private void UpdateAudio()
+ {
+ try
+ {
+ float throttle = Engine.GetActualThrottle();
+ _engineSound.SetEngineState(Engine.RPM, throttle);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Audio update error: {ex.Message}");
+ }
+ }
+
+ private void UpdateVehicleMotion(float deltaTime)
+ {
+ // Calculate net force
+ float tractiveForce = CalculateTractiveForce();
+ float resistanceForce = CalculateTotalResistanceForce();
+ float netForce = tractiveForce - resistanceForce;
+
+ // Calculate acceleration: a = F / m
+ float acceleration = netForce / Mass;
+
+ // Update velocity: v = v₀ + a·Δt
+ if (Velocity.Length > 0)
+ {
+ Vector2 direction = Velocity.Normalized();
+ float newSpeed = Velocity.Length + acceleration * deltaTime;
+ newSpeed = Math.Max(newSpeed, 0); // Don't go backwards without reverse gear
+ Velocity = direction * newSpeed;
+ }
+ else
+ {
+ // Starting from standstill
+ Velocity = new Vector2(acceleration * deltaTime, 0);
+ }
+
+ Position += Velocity * deltaTime;
+
+ // Sync wheel speed with actual vehicle speed (with slip allowance)
+ float currentWheelSpeed = Velocity.Length;
+ WheelSystem.SetSpeed(currentWheelSpeed);
+ }
+
+ private float CalculateTractiveForce()
+ {
+ // 1. Get the torque available at the wheels
+ float wheelTorque = Drivetrain.ClutchTorque * Drivetrain.Efficiency;
+
+ // 2. Convert torque to theoretical force: F = τ / r
+ float theoreticalForce = wheelTorque / WheelSystem.Radius;
+
+ // 3. Account for weight distribution and driven wheels
+ // Normal load on driven wheels = (DrivenWheels / WheelCount) * Weight
+ float drivenWheelNormalLoad = (DrivenWheels / (float)WheelCount) * Mass * 9.81f;
+
+ // 4. Calculate maximum tractive force based on friction (tire grip)
+ float frictionCoefficient = 1.2f; // Typical tire on dry asphalt
+ float maxTractiveForce = drivenWheelNormalLoad * frictionCoefficient;
+
+ // 5. Limit the force by what the tires can actually grip
+ // Also handle direction (forward/reverse)
+ if (theoreticalForce > 0)
+ {
+ return Math.Min(theoreticalForce, maxTractiveForce);
+ }
+ else
+ {
+ // For reverse or engine braking
+ return Math.Max(theoreticalForce, -maxTractiveForce);
+ }
+ }
+
+ private void ApplyBraking(float deltaTime)
+ {
+ if (BrakeInput <= 0) return;
+
+ float brakeTorque = BrakeInput * 500f; // 500 Nm max brake torque
+
+ WheelSystem.ApplyTorque(-brakeTorque, deltaTime);
+ }
+
+ public float CalculateTotalResistanceForce()
+ {
+ float dragForce = CalculateDragForce();
+ float rollingForce = CalculateRollingResistanceForce();
+ return dragForce + rollingForce;
+ }
+
+ private float CalculateDragForce()
+ {
+ // F_drag = 0.5 * ρ * Cd * A * v²
+ float speed = Speed;
+ return 0.5f * AirDensity * DragCoefficient * FrontalArea * speed * speed;
+ }
+
+ private float CalculateRollingResistanceForce()
+ {
+ // F_rolling = C_r * m * g
+ return RollingResistanceCoefficient * Mass * 9.81f;
+ }
+
+ // Convert resistance force to wheel torque
+ public float CalculateResistanceTorque()
+ {
+ float totalForce = CalculateTotalResistanceForce();
+ return totalForce * WheelSystem.Radius;
+ }
+
+ public void DisplayUpdate()
+ {
+ Console.SetCursorPosition(0, 0);
+ Console.WriteLine($"Engine Energy: {Engine.FlywheelEnergy,7:F0} J");
+ Console.WriteLine($"Engine Torque: {Engine.GetTorqueOutput(),7:F0} Nm");
+ Console.WriteLine($"Engine RPM: {Engine.RPM,7:F0}");
+ Console.WriteLine($"Wheel Energy: {WheelSystem.WheelEnergy,7:F0} J");
+ Console.WriteLine($"Wheel RPM: {WheelSystem.RPM,7:F0}");
+ Console.WriteLine($"Vehicle: {Speed * 3.6f,7:F1} km/h");
+ Console.WriteLine($"Throttle: {Engine.GetActualThrottle() * 100,6:F1}%");
+ Console.WriteLine($"Power: {Engine.CurrentPower / 1000,6:F1} kW");
+ Console.WriteLine($"Transmitted: {Drivetrain.TransmittedPower / 1000,6:F1} kW");
+ Console.WriteLine($"Brake: {BrakeInput * 100,6:F1}%");
+ Console.WriteLine($"Clutch: {ClutchInput * 100,6:F1}% disengaged");
+ Console.WriteLine($"Speed Diff: {Drivetrain.GetSpeedDifferenceRPM(),6:F0} RPM");
+ Console.WriteLine($"Clutch T: {Drivetrain.ClutchTorque,6:F0} Nm");
+ Console.WriteLine($"Resistance: {CalculateTotalResistanceForce(),6:F1} N");
+ Console.WriteLine($"Drag: {CalculateDragForce(),6:F1} N");
+ Console.WriteLine($"Rolling: {CalculateRollingResistanceForce(),6:F1} N");
+ Console.WriteLine($"Gear: {Drivetrain.GetCurrentGearName(),3} (Ratio: {Drivetrain.GearRatio:F2}:1)");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Car simulation/Drivetrain.cs b/Car simulation/Drivetrain.cs
new file mode 100644
index 0000000..fc71698
--- /dev/null
+++ b/Car simulation/Drivetrain.cs
@@ -0,0 +1,206 @@
+namespace Car_simulation
+{
+ public class Drivetrain
+ {
+ // Connected components
+ public Engine Engine { get; private set; }
+ public WheelSystem WheelSystem { get; private set; }
+
+ private int currentGear = 1;
+ public float[] GearRatios { get; set; } =
+ {
+ 3.8f, // 1st - Lower for better launch
+ 2.5f, // 2nd
+ 1.8f, // 3rd
+ 1.3f, // 4th
+ 1.0f, // 5th - Direct drive
+ 0.8f, // 6th - Overdrive
+ 0.65f // 7th - Double overdrive (optional)
+ };
+ public float FinalDriveRatio { get; set; } = 5.0f;
+ public float Efficiency { get; set; } = 0.95f;
+ public float ClutchEngagement { get; set; } = 0f; // 0 = disengaged, 1 = fully engaged
+
+ // Calculated
+ public float GearRatio => GetCurrentGearRatio();
+ public float TotalRatio => GearRatio * FinalDriveRatio;
+
+ // Clutch properties
+ public float ClutchStiffness { get; set; } = 500f; // Nm/(rad/s) - how strongly clutch pulls speeds together
+ public float MaxClutchTorque { get; set; } = 4500f; // Maximum torque clutch can transmit
+
+ // State
+ public float SpeedDifference { get; private set; } // rad/s
+ public float ClutchTorque { get; private set; }
+ public float TransmittedPower { get; private set; }
+
+ private float previousWheelOmega = 0f;
+
+ public Drivetrain(Engine engine, WheelSystem wheelSystem)
+ {
+ Engine = engine;
+ WheelSystem = wheelSystem;
+ previousWheelOmega = wheelSystem.AngularVelocity;
+ }
+
+ public void GearUp()
+ {
+ if (currentGear < GearRatios.Length)
+ currentGear++;
+ }
+
+ public void GearDown()
+ {
+ if (currentGear > 1)
+ currentGear--;
+ }
+
+ private float GetCurrentGearRatio()
+ {
+ if (currentGear == 0) return 0f; // Neutral
+ if (currentGear == -1) return -3.5f; // Reverse (example ratio)
+ if (currentGear > 0 && currentGear <= GearRatios.Length)
+ return GearRatios[currentGear - 1];
+ return 0f; // Invalid gear
+ }
+
+ public float CalculateSpeedDifference()
+ {
+ if (TotalRatio == 0) return 0f;
+
+ float engineOmega = Engine.AngularVelocity;
+ float wheelOmega = WheelSystem.AngularVelocity;
+ float expectedWheelOmega = engineOmega / TotalRatio;
+
+ SpeedDifference = wheelOmega - expectedWheelOmega;
+ return SpeedDifference;
+ }
+
+ public float CalculateClutchTorque()
+ {
+ if (ClutchEngagement <= 0.01f)
+ {
+ ClutchTorque = 0;
+ return 0f;
+ }
+
+ CalculateSpeedDifference();
+
+ float torque = -SpeedDifference * ClutchStiffness * ClutchEngagement;
+ torque = Math.Clamp(torque, -MaxClutchTorque, MaxClutchTorque);
+
+ float actualThrottle = Engine.GetActualThrottle();
+ float availableEngineTorque = Engine.GetTorqueOutput();
+
+ float maxTorqueAtClutch = maxEngineTorque * TotalRatio * Efficiency;
+
+ torque = maxTorqueAtClutch;
+
+
+ ClutchTorque = torque;
+ return torque;
+ }
+
+ public void ApplyDrivetrainWork(float deltaTime)
+ {
+ if (ClutchEngagement <= 0.01f || TotalRatio == 0)
+ {
+ ClutchTorque = 0;
+ TransmittedPower = 0;
+ return;
+ }
+
+ CalculateSpeedDifference();
+ float clutchTorque = CalculateClutchTorque();
+
+ bool engineDrivingWheels = clutchTorque > 0;
+ bool wheelsDrivingEngine = clutchTorque < 0;
+
+ if (engineDrivingWheels)
+ {
+ // Engine -> Wheels (normal driving)
+ ApplyEngineToWheels(clutchTorque, deltaTime);
+ }
+ else if (wheelsDrivingEngine)
+ {
+ // Wheels -> Engine (engine braking)
+ ApplyWheelsToEngine(clutchTorque, deltaTime);
+ }
+
+ TransmittedPower = clutchTorque * SpeedDifference;
+ }
+
+ private void ApplyEngineToWheels(float clutchTorque, float deltaTime)
+ {
+ // Existing logic for engine driving wheels
+ float netWheelTorque = clutchTorque * Efficiency - WheelSystem.ResistanceTorque;
+ float netEngineTorque = -clutchTorque / TotalRatio;
+
+ // Apply to both
+ Engine.ApplyTorque(netEngineTorque, deltaTime);
+ WheelSystem.ApplyTorque(netWheelTorque, deltaTime);
+ }
+
+ private void ApplyWheelsToEngine(float clutchTorque, float deltaTime)
+ {
+ // Wheels driving engine (engine braking)
+ // Negative clutchTorque means wheels are trying to spin engine faster
+
+ float wheelTorque = clutchTorque; // Negative value
+ float engineTorque = -clutchTorque / TotalRatio; // Positive resistance
+
+ // Apply resistance to wheels
+ WheelSystem.ApplyTorque(wheelTorque, deltaTime);
+
+ Engine.ApplyTorque(-engineTorque, deltaTime); // Negative = slowing
+ }
+
+ public float GetEquivalentInertiaAtEngine()
+ {
+ float wheelInertia = WheelSystem.GetTotalInertia();
+ return Engine.MomentOfInertia + (wheelInertia * TotalRatio * TotalRatio);
+ }
+
+ public float CalculateEngineLoad(float deltaTime)
+ {
+ if (ClutchEngagement <= 0.01f) return 0f;
+
+ float wheelResistanceTorque = WheelSystem.ResistanceTorque;
+ float engineLoadTorque = wheelResistanceTorque / (TotalRatio * Efficiency);
+
+ float inertiaLoad = CalculateInertiaLoad(deltaTime);
+
+ return engineLoadTorque + inertiaLoad;
+ }
+
+ private float CalculateInertiaLoad(float deltaTime)
+ {
+ float wheelAlpha = (WheelSystem.AngularVelocity - previousWheelOmega) / deltaTime;
+ previousWheelOmega = WheelSystem.AngularVelocity;
+
+ float inertiaTorque = wheelAlpha * WheelSystem.GetTotalInertia();
+ return inertiaTorque / (TotalRatio * TotalRatio * Efficiency);
+ }
+
+ public void Update(float deltaTime)
+ {
+ ApplyDrivetrainWork(deltaTime);
+ }
+
+ // Helper methods
+ public float GetSpeedDifferenceRPM()
+ {
+ return SpeedDifference * PhysicsUtil.RAD_PER_SEC_TO_RPM;
+ }
+
+ public string GetCurrentGearName()
+ {
+ return currentGear switch
+ {
+ -1 => "R",
+ 0 => "N",
+ _ => currentGear.ToString()
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Car simulation/Engine.cs b/Car simulation/Engine.cs
new file mode 100644
index 0000000..9308aee
--- /dev/null
+++ b/Car simulation/Engine.cs
@@ -0,0 +1,143 @@
+namespace Car_simulation
+{
+ public class Engine
+ {
+ // Energy state
+ public float FlywheelEnergy { get; set; } // Joules
+
+ // Values
+ public float RPM => GetRPM();
+ public float AngularVelocity => GetOmega();
+ public float CurrentPower { get; private set; }
+
+ // Physical properties
+ public float MomentOfInertia { get; set; } = 0.25f; // kg·m²
+ public float IdleRPM { get; set; } = 800f;
+ public float StallSpeed { get; set; } = 200f;
+ public float Throttle { get; set; } = 0f;
+ public bool IsRunning => RPM > StallSpeed;
+
+ // Torque characteristics
+ public Dictionary TorqueCurve { get; set; } = new()
+ {
+ // RPM - Torque Nm
+ { 0f, 0f },
+ { 800f, 150f }, // Idle
+ { 2000f, 200f }, // Peak torque
+ { 4500f, 250f },
+ { 7200f, 250f },
+ { 9200f, 250f },
+ { 10000f, 200f },
+ { 11000f, 0f }
+ };
+
+ public Engine()
+ {
+ // Start with idle energy
+ FlywheelEnergy = GetEnergyFromRPM(IdleRPM);
+ }
+
+ // Calculations
+
+ public float CalculateFrictionEnergy(float deltaTime)
+ {
+ // Real friction torque data for 2.0L engine (Nm)
+ float frictionTorque;
+
+ if (RPM < 500) frictionTorque = 15f; // Static/breakaway
+ else if (RPM < 1000) frictionTorque = 14f;
+ else if (RPM < 2000) frictionTorque = 16f;
+ else if (RPM < 3000) frictionTorque = 18f;
+ else if (RPM < 4000) frictionTorque = 21f;
+ else if (RPM < 5000) frictionTorque = 25f;
+ else if (RPM < 6000) frictionTorque = 30f;
+ else if (RPM < 7000) frictionTorque = 36f;
+ else frictionTorque = 44f;
+
+ float frictionPower = frictionTorque * AngularVelocity;
+ return frictionPower * deltaTime;
+ }
+
+ private float CalculateCombustionEnergy(float deltaTime)
+ {
+ float torque = GetTorqueOutput() * GetActualThrottle();
+ return torque * AngularVelocity * deltaTime;
+ }
+
+ private float CalculateLoadEnergy(float deltaTime, float loadTorque)
+ {
+ return loadTorque * AngularVelocity * deltaTime;
+ }
+
+ // Get
+
+ public float GetActualThrottle()
+ {
+ float idleThrottle = Math.Max((IdleRPM - RPM) / 10, 0);
+ return Math.Clamp(Throttle + idleThrottle, 0, 1);
+ }
+
+ public float GetOmega()
+ {
+ if (FlywheelEnergy <= 0) return 0;
+ return MathF.Sqrt(2f * FlywheelEnergy / MomentOfInertia);
+ }
+
+ public float GetRPM()
+ {
+ return GetOmega() * PhysicsUtil.RAD_PER_SEC_TO_RPM;
+ }
+
+ // Set
+
+ public float GetEnergyFromRPM(float rpm)
+ {
+ float omega = rpm * PhysicsUtil.RPM_TO_RAD_PER_SEC;
+ return 0.5f * MomentOfInertia * omega * omega;
+ }
+
+ // torque curve
+ public float GetTorqueOutput()
+ {
+ if (RPM <= 0) return 0;
+
+ var points = TorqueCurve.OrderBy(p => p.Key).ToList();
+
+ if (RPM <= points.First().Key) return points.First().Value;
+ if (RPM >= points.Last().Key) return points.Last().Value;
+
+ for (int i = 0; i < points.Count - 1; i++)
+ {
+ if (RPM >= points[i].Key && RPM <= points[i + 1].Key)
+ {
+ float t = (RPM - points[i].Key) / (points[i + 1].Key - points[i].Key);
+ return PhysicsUtil.Lerp(points[i].Value, points[i + 1].Value, t);
+ }
+ }
+
+ return 0f;
+ }
+
+ public void ApplyTorque(float torque, float deltaTime)
+ {
+ if (torque == 0) return;
+
+ float work = torque * AngularVelocity * deltaTime;
+
+ FlywheelEnergy += work;
+ FlywheelEnergy = Math.Max(FlywheelEnergy, 0);
+ }
+
+ public void Update(float deltaTime, float loadTorque)
+ {
+ float combustionEnergy = CalculateCombustionEnergy(deltaTime);
+ float frictionEnergy = CalculateFrictionEnergy(deltaTime);
+ float loadEnergy = CalculateLoadEnergy(deltaTime, loadTorque);
+
+ float netEnergy = combustionEnergy - frictionEnergy - loadEnergy;
+ CurrentPower = netEnergy / deltaTime;
+ FlywheelEnergy += netEnergy;
+ FlywheelEnergy = Math.Max(FlywheelEnergy, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Car simulation/EngineSound.cs b/Car simulation/EngineSound.cs
new file mode 100644
index 0000000..bd8a66b
--- /dev/null
+++ b/Car simulation/EngineSound.cs
@@ -0,0 +1,149 @@
+using SFML.Audio;
+using SFML.System;
+using System;
+
+namespace Car_simulation
+{
+ public class EngineSound : SoundStream
+ {
+ // Audio properties - smaller buffer for less latency
+ private const uint SAMPLE_RATE = 44100;
+ private const ushort CHANNEL_COUNT = 2; // Stereo
+ private const float BUFFER_DURATION = 0.01f; // 10ms instead of 50ms!
+
+ // Engine sound properties - NO SMOOTHING for instant response
+ private volatile float _currentRPM = 800f; // volatile for thread safety
+ private volatile float _currentThrottle = 0f;
+ private float _volume = 0.3f;
+ private bool _isPlaying = false;
+
+ // Harmonic series - DIRECT RPM TO FREQUENCY
+ private float[] _harmonicRatios = { 1f, 2f, 4f, 6f };
+ private float[] _harmonicAmplitudes = { 1f, 0.3f, 0.1f, 0.05f };
+ private float[] _harmonicPhases = new float[4];
+
+ // Engine configuration - for direct RPM calculation
+ public int CylinderCount { get; set; } = 4;
+ public float FiringFrequencyMultiplier => CylinderCount / 2f; // 4-stroke engines
+
+ // For RPM to frequency mapping
+ private float _rpmToHzFactor;
+
+ private Random _random = new Random();
+
+ public EngineSound()
+ {
+ Initialize(CHANNEL_COUNT, SAMPLE_RATE);
+
+ // Calculate direct conversion factor
+ // RPM to Hz: (RPM / 60) × (Cylinders / 2) for 4-stroke
+ _rpmToHzFactor = (1f / 60f) * (CylinderCount / 2f);
+
+ // Initialize phases
+ for (int i = 0; i < _harmonicPhases.Length; i++)
+ {
+ _harmonicPhases[i] = (float)(_random.NextDouble() * 2 * Math.PI);
+ }
+
+ Console.WriteLine($"EngineSound initialized: {BUFFER_DURATION * 1000:F0}ms buffer, {CylinderCount} cylinders");
+ }
+
+ // CALL THIS FROM YOUR PHYSICS THREAD - INSTANT UPDATE
+ public void SetEngineState(float rpm, float throttle)
+ {
+ // NO LOCK, NO SMOOTHING - DIRECT ASSIGNMENT
+ _currentRPM = rpm;
+ _currentThrottle = throttle;
+
+ // Volume based on throttle (instant)
+ _volume = 0.1f + 0.4f * throttle;
+ }
+
+ public void StartSound()
+ {
+ if (!_isPlaying)
+ {
+ Play();
+ _isPlaying = true;
+ }
+ }
+
+ public void StopSound()
+ {
+ if (_isPlaying)
+ {
+ Stop();
+ _isPlaying = false;
+ }
+ }
+
+ protected override bool OnGetData(out short[] samples)
+ {
+ // SMALLER BUFFER: 10ms instead of 50ms
+ int sampleCount = (int)(SAMPLE_RATE * BUFFER_DURATION) * 2; // *2 for stereo
+ samples = new short[sampleCount];
+
+ // Get current values ONCE per buffer (not per sample)
+ float rpm = _currentRPM;
+ float throttle = _currentThrottle;
+ float volume = _volume;
+
+ // DIRECT RPM TO FREQUENCY - NO SMOOTHING
+ float baseFrequency = rpm * _rpmToHzFactor; // (RPM/60) × (cylinders/2)
+
+ // Pre-calculate harmonic frequencies
+ float[] harmonicFrequencies = new float[_harmonicRatios.Length];
+ float[] phaseIncrements = new float[_harmonicRatios.Length];
+
+ for (int h = 0; h < _harmonicRatios.Length; h++)
+ {
+ harmonicFrequencies[h] = baseFrequency * _harmonicRatios[h];
+ phaseIncrements[h] = harmonicFrequencies[h] * 2f * MathF.PI / SAMPLE_RATE;
+ }
+
+ // Calculate roughness factor
+ float roughness = 0.02f * throttle;
+
+ // Generate sound
+ for (int i = 0; i < sampleCount; i += 2)
+ {
+ float sampleValue = 0f;
+
+ // Sum all harmonics
+ for (int h = 0; h < _harmonicRatios.Length; h++)
+ {
+ sampleValue += MathF.Sin(_harmonicPhases[h]) * _harmonicAmplitudes[h];
+ _harmonicPhases[h] += phaseIncrements[h];
+
+ if (_harmonicPhases[h] > 2f * MathF.PI)
+ _harmonicPhases[h] -= 2f * MathF.PI;
+ }
+
+ // Add roughness
+ sampleValue += (float)(_random.NextDouble() * 2 - 1) * roughness;
+
+ // Apply volume
+ sampleValue *= volume;
+
+ // Clamp and convert
+ sampleValue = Math.Clamp(sampleValue, -1f, 1f);
+ short sample = (short)(sampleValue * 32767);
+
+ // Stereo
+ samples[i] = sample;
+ samples[i + 1] = sample;
+ }
+
+ return true;
+ }
+
+ protected override void OnSeek(Time timeOffset)
+ {
+ // Reset phases
+ for (int i = 0; i < _harmonicPhases.Length; i++)
+ {
+ _harmonicPhases[i] = (float)(_random.NextDouble() * 2 * Math.PI);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Car simulation/IPhysicsObject.cs b/Car simulation/IPhysicsObject.cs
new file mode 100644
index 0000000..19c459d
--- /dev/null
+++ b/Car simulation/IPhysicsObject.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace Car_simulation
+{
+ public interface IPhysicsObject
+ {
+ Vector2 Position { get; }
+ float GetResistanceForce(Vector2 carPosition, float carSpeed);
+ float GetTractionCoefficient(Vector2 carPosition);
+ }
+}
diff --git a/Car simulation/Program.cs b/Car simulation/Program.cs
new file mode 100644
index 0000000..68a4770
--- /dev/null
+++ b/Car simulation/Program.cs
@@ -0,0 +1,211 @@
+using Car_simulation;
+using SFML.Window;
+using SFML.Graphics;
+using SFML.System;
+using System.Diagnostics;
+
+internal class Program
+{
+ Car car = new Car();
+ private bool _isRunning = true;
+
+ private RenderWindow _window;
+
+ // Timing for physics
+ private Clock _clock = new Clock();
+ private Time _timePerUpdate = Time.FromSeconds(1.0f / 60.0f); // 60 FPS physics
+ private Time _accumulatedTime = Time.Zero;
+ private long _updateCount = 0;
+
+ private Dictionary _previousKeyStates = new Dictionary();
+ private Dictionary _currentKeyStates = new Dictionary();
+
+ private static void Main(string[] args)
+ {
+ Program program = new Program();
+ program.Run();
+ }
+
+ private void Run()
+ {
+ _window = new RenderWindow(new VideoMode(800, 600), "Car Simulation", Styles.Default);
+ _window.SetVisible(true);
+ _window.SetFramerateLimit(60);
+ _window.SetKeyRepeatEnabled(false);
+
+ _window.Closed += (sender, e) => _isRunning = false;
+ _window.KeyPressed += OnKeyPressed;
+ _window.KeyReleased += OnKeyReleased;
+
+ InitializeTrackedKeys();
+
+ _clock.Restart();
+
+ while (_isRunning && _window.IsOpen)
+ {
+ _window.DispatchEvents();
+
+ Time elapsed = _clock.Restart();
+ _accumulatedTime += elapsed;
+
+ while (_accumulatedTime >= _timePerUpdate)
+ {
+ ProcessInput(_timePerUpdate.AsSeconds());
+ car.Update(_timePerUpdate.AsSeconds());
+ _accumulatedTime -= _timePerUpdate;
+ _updateCount++;
+
+ if (_accumulatedTime >= Time.FromSeconds(0.2f))
+ {
+ _accumulatedTime = _timePerUpdate;
+ }
+ }
+
+ UpdateDisplay();
+ UpdatePreviousKeyStates();
+ }
+
+ _window.Close();
+ Console.WriteLine($"\nSimulation stopped after {_updateCount} updates");
+ }
+
+ private void InitializeTrackedKeys()
+ {
+ // Initialize all keys we care about
+ var keysToTrack = new Keyboard.Key[]
+ {
+ Keyboard.Key.W,
+ Keyboard.Key.Up,
+ Keyboard.Key.Down,
+ Keyboard.Key.B,
+ Keyboard.Key.Space,
+ Keyboard.Key.Left,
+ Keyboard.Key.Right,
+ Keyboard.Key.Escape
+ };
+
+ foreach (var key in keysToTrack)
+ {
+ _currentKeyStates[key] = false;
+ _previousKeyStates[key] = false;
+ }
+ }
+
+ private void OnKeyPressed(object sender, KeyEventArgs e)
+ {
+ var key = e.Code;
+
+ // Update current state
+ if (_currentKeyStates.ContainsKey(key))
+ {
+ _currentKeyStates[key] = true;
+ }
+ }
+
+ private void OnKeyReleased(object sender, KeyEventArgs e)
+ {
+ var key = e.Code;
+
+ // Update current state
+ if (_currentKeyStates.ContainsKey(key))
+ {
+ _currentKeyStates[key] = false;
+ }
+ }
+
+ private void ProcessInput(float deltaTime)
+ {
+ // quit
+ if (IsKeyDown(Keyboard.Key.Escape))
+ {
+ _isRunning = false;
+ return;
+ }
+
+ // force clutch
+ car.ForceClutch = (IsKeyDown(Keyboard.Key.Space));
+
+ // throttle
+ if (IsKeyDown(Keyboard.Key.W))
+ {
+ car.ThrottleInput = Math.Min(car.ThrottleInput + 2f * deltaTime, 1.0f);
+ }
+ else
+ {
+ car.ThrottleInput = Math.Max(car.ThrottleInput - 10f * deltaTime, 0f);
+ }
+
+ // brake
+ if (IsKeyDown(Keyboard.Key.B))
+ {
+ car.BrakeInput = Math.Min(car.BrakeInput + 0.5f * deltaTime, 1.0f);
+ }
+ else
+ {
+ car.BrakeInput = Math.Max(car.BrakeInput - 1f * deltaTime, 0f);
+ }
+
+ // clutch
+ if (IsKeyDown(Keyboard.Key.Up))
+ {
+ car.ClutchInput = Math.Min(car.ClutchInput + 1f * deltaTime, 1.0f);
+ }
+ else if (IsKeyDown(Keyboard.Key.Down))
+ {
+ car.ClutchInput = Math.Max(car.ClutchInput - 1f * deltaTime, 0f);
+ }
+
+ // clutch
+ if (IsKeyDown(Keyboard.Key.Up))
+ {
+ car.ClutchInput = Math.Min(car.ClutchInput + 1f * deltaTime, 1.0f);
+ }
+ else if (IsKeyDown(Keyboard.Key.Down))
+ {
+ car.ClutchInput = Math.Max(car.ClutchInput - 1f * deltaTime, 0f);
+ }
+
+ // gear
+ if (WasKeyPressed(Keyboard.Key.Left))
+ {
+ car.Drivetrain.GearDown();
+ }
+ else if (WasKeyPressed(Keyboard.Key.Right))
+ {
+ car.Drivetrain.GearUp();
+ }
+ }
+
+ private void UpdatePreviousKeyStates()
+ {
+ var keys = new List(_currentKeyStates.Keys);
+ foreach (var key in keys)
+ {
+ _previousKeyStates[key] = _currentKeyStates[key];
+ }
+ }
+
+ private bool IsKeyDown(Keyboard.Key key)
+ {
+ return _currentKeyStates.ContainsKey(key) && _currentKeyStates[key];
+ }
+
+ private bool WasKeyPressed(Keyboard.Key key)
+ {
+ return IsKeyDown(key) &&
+ (!_previousKeyStates.ContainsKey(key) || !_previousKeyStates[key]);
+ }
+
+ private void UpdateDisplay()
+ {
+ _window.Clear(Color.Black);
+
+ // Render car or simulation visualization here
+ // For example, if car has Draw() method:
+ // car.Draw(_window);
+
+ car.DisplayUpdate(); // If this updates console display
+
+ _window.Display();
+ }
+}
\ No newline at end of file
diff --git a/Car simulation/Util.cs b/Car simulation/Util.cs
new file mode 100644
index 0000000..32e8678
--- /dev/null
+++ b/Car simulation/Util.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Car_simulation
+{
+ public class Util
+ {
+ public static float Lerp(float a, float b, float t)
+ {
+ return a + (b - a) * t;
+ }
+ }
+
+ public static class PhysicsUtil
+ {
+ public const float G = 9.81f;
+ public const float AirDensity = 1.225f;
+
+ public static float Lerp(float a, float b, float t)
+ {
+ t = Math.Clamp(t, 0f, 1f);
+ return a + (b - a) * t;
+ }
+
+ public static float RPMToOmega(float rpm) => rpm * MathF.PI * 2f / 60f;
+ public static float OmegaToRPM(float omega) => omega * 60f / (2f * MathF.PI);
+
+ // Calculate kinetic energy: 0.5 * I * ω²
+ public static float CalculateRotationalEnergy(float inertia, float omega)
+ {
+ return 0.5f * inertia * omega * omega;
+ }
+
+ // Calculate omega from energy: ω = sqrt(2E / I)
+ public static float CalculateOmegaFromEnergy(float energy, float inertia)
+ {
+ if (energy <= 0) return 0;
+ return MathF.Sqrt(2f * energy / inertia);
+ }
+
+ public const float RAD_PER_SEC_TO_RPM = 60f / (2f * MathF.PI); // ≈ 9.549
+ public const float RPM_TO_RAD_PER_SEC = (2f * MathF.PI) / 60f; // ≈ 0.1047
+ }
+}
diff --git a/Car simulation/Vector2.cs b/Car simulation/Vector2.cs
new file mode 100644
index 0000000..6f6ad9d
--- /dev/null
+++ b/Car simulation/Vector2.cs
@@ -0,0 +1,39 @@
+public struct Vector2
+{
+ public float X, Y;
+
+ public Vector2(float x, float y) { X = x; Y = y; }
+
+ public float Length => MathF.Sqrt(X * X + Y * Y);
+ public float LengthSquared => X * X + Y * Y;
+
+ // Returns a normalized copy
+ public Vector2 Normalized()
+ {
+ float length = Length;
+ if (length > 0.0001f)
+ return new Vector2(X / length, Y / length);
+ return new Vector2(0, 0);
+ }
+
+ // Normalizes in place
+ public void Normalize()
+ {
+ float length = Length;
+ if (length > 0.0001f)
+ {
+ X /= length;
+ Y /= length;
+ }
+ }
+
+ // Static normalize
+ public static Vector2 Normalize(Vector2 v) => v.Normalized();
+
+ // Operators
+ public static Vector2 operator *(Vector2 v, float s) => new Vector2(v.X * s, v.Y * s);
+ public static Vector2 operator *(float s, Vector2 v) => new Vector2(v.X * s, v.Y * s);
+ public static Vector2 operator /(Vector2 v, float s) => new Vector2(v.X / s, v.Y / s);
+ public static Vector2 operator +(Vector2 a, Vector2 b) => new Vector2(a.X + b.X, a.Y + b.Y);
+ public static Vector2 operator -(Vector2 a, Vector2 b) => new Vector2(a.X - b.X, a.Y - b.Y);
+}
\ No newline at end of file
diff --git a/Car simulation/WheelSystem.cs b/Car simulation/WheelSystem.cs
new file mode 100644
index 0000000..2f1d710
--- /dev/null
+++ b/Car simulation/WheelSystem.cs
@@ -0,0 +1,92 @@
+namespace Car_simulation
+{
+ public class WheelSystem
+ {
+ // Physical properties
+ public float Radius { get; set; } = 0.3f; // meters
+ public float Inertia { get; set; } = 2.0f; // kg·m² per wheel
+ public int WheelCount { get; set; } = 4;
+ public int DrivenWheels { get; set; } = 2; // 2WD
+
+ // State
+ public float WheelEnergy { get; set; } = 0f; // Joules
+ public float AngularVelocity => GetOmega();
+ public float RPM => GetRPM();
+ public float Speed => GetSpeed();
+ public float ResistanceTorque { get; set; } = 0f;
+
+ // Calculations
+ public float GetTotalInertia()
+ {
+ return Inertia * WheelCount;
+ }
+
+ public float GetOmega()
+ {
+ if (WheelEnergy <= 0 || GetTotalInertia() <= 0) return 0f;
+ return MathF.Sqrt(2f * WheelEnergy / GetTotalInertia());
+ }
+
+ public float GetRPM()
+ {
+ return AngularVelocity * PhysicsUtil.RAD_PER_SEC_TO_RPM;
+ }
+
+ public float GetSpeed()
+ {
+ return AngularVelocity * Radius;
+ }
+
+ public float GetEnergyFromSpeed(float speed)
+ {
+ float omega = speed / Radius;
+ return 0.5f * GetTotalInertia() * omega * omega;
+ }
+
+ public void SetSpeed(float speed)
+ {
+ WheelEnergy = GetEnergyFromSpeed(speed);
+ }
+
+ // Apply work to the wheels
+ public void ApplyWork(float work)
+ {
+ WheelEnergy += work;
+ WheelEnergy = Math.Max(WheelEnergy, 0);
+ }
+
+ public void ApplyTorque(float torque, float deltaTime)
+ {
+ if (torque == 0) return;
+ float work = torque * AngularVelocity * deltaTime;
+ ApplyWork(work);
+ }
+
+ public void ApplyResistance(float deltaTime)
+ {
+ if (ResistanceTorque <= 0 || AngularVelocity == 0) return;
+
+ float omega = AngularVelocity;
+
+ if (MathF.Abs(omega) < 0.1f)
+ {
+ // Check if we have enough torque to overcome static friction
+ // For now, just return without applying resistance to allow startup
+ return;
+ }
+
+ float resistanceSign = -MathF.Sign(omega);
+ float alpha = (resistanceSign * ResistanceTorque) / GetTotalInertia();
+
+ float omegaNew = omega + alpha * deltaTime;
+
+ if (MathF.Sign(omegaNew) != MathF.Sign(omega))
+ {
+ omegaNew = 0;
+ }
+
+ float energyNew = 0.5f * GetTotalInertia() * omegaNew * omegaNew;
+ WheelEnergy = Math.Max(energyNew, 0);
+ }
+ }
+}
\ No newline at end of file