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)"); } } }