diff --git a/Car simulation/Car.cs b/Car simulation/Car.cs index 0ecd04e..3f9ae6f 100644 --- a/Car simulation/Car.cs +++ b/Car simulation/Car.cs @@ -5,10 +5,11 @@ 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 Vector2 Velocity => new Vector2(WheelSystem.CarSpeed, 0); // Now directly from wheels - public float Mass = 1500f; // kg + public float Speed => WheelSystem.CarSpeed; + + public float Mass { get; set; } = 1500f; // kg public int WheelCount = 4; public int DrivenWheels = 2; @@ -20,7 +21,7 @@ namespace Car_simulation // Aerodynamics private const float AirDensity = 1.225f; - public float DragCoefficient = 0.1f; + public float DragCoefficient = 0.3f; public float FrontalArea = 2.2f; // m² public float RollingResistanceCoefficient = 0.015f; @@ -36,12 +37,14 @@ namespace Car_simulation { Engine = new Engine(); WheelSystem = new WheelSystem(); - Drivetrain = new Drivetrain(Engine, WheelSystem); - // Initial setup + // Set car mass in wheel system (so it's included in energy calculations) + WheelSystem.CarMass = Mass; WheelSystem.WheelCount = WheelCount; WheelSystem.DrivenWheels = DrivenWheels; + Drivetrain = new Drivetrain(Engine, WheelSystem); + InitializeAudio(); } @@ -52,7 +55,6 @@ namespace Car_simulation _engineSound = new EngineSound(); _engineSound.SetEngineState(Engine.IdleRPM, 0f); _engineSound.StartSound(); - } catch (Exception ex) { @@ -64,27 +66,30 @@ namespace Car_simulation public void Update(float deltaTime) { Engine.Throttle = ThrottleInput; - Drivetrain.ClutchEngagement = 1f - ClutchInput; // Convert: 0 input = 1 engagement + Drivetrain.ClutchEngagement = 1f - ClutchInput; if (ForceClutch) Drivetrain.ClutchEngagement = 0f; - float resistanceTorque = CalculateResistanceTorque(); - WheelSystem.ResistanceTorque = resistanceTorque; + // Update engine + Engine.Update(deltaTime); + // Update drivetrain (transfers energy between engine and wheels+car) Drivetrain.Update(deltaTime); + + // Calculate and apply resistance + float resistanceForce = CalculateTotalResistanceForce(); + WheelSystem.ResistanceTorque = resistanceForce * WheelSystem.Radius; WheelSystem.ApplyResistance(deltaTime); - float engineLoad = Drivetrain.CalculateEngineLoad(deltaTime); - Engine.Update(deltaTime, engineLoad); - - UpdateVehicleMotion(deltaTime); + // Apply braking ApplyBraking(deltaTime); + // Update position based on velocity (which comes from WheelSystem) + Position += Velocity * deltaTime; + if (_audioEnabled) - { UpdateAudio(); - } } private void UpdateAudio() @@ -100,72 +105,11 @@ namespace Car_simulation } } - 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 - + float brakeTorque = BrakeInput * 3000f; WheelSystem.ApplyTorque(-brakeTorque, deltaTime); } @@ -178,40 +122,34 @@ namespace Car_simulation 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($"Total Energy: {WheelSystem.TotalEnergy,7:F0} J"); + Console.WriteLine($" (Wheel Rot: {WheelSystem.GetRotationalEnergy(),7:F0} J)"); + Console.WriteLine($" (Car Trans: {WheelSystem.GetTranslationalEnergy(),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: {ClutchInput * 100,6:F1}% disengaged"); Console.WriteLine($"Clutch T: {Drivetrain.ClutchTorque,6:F0} Nm"); + Console.WriteLine($"Clutch Slip: {Drivetrain.GetClutchSlipPercent(),6:F1}%"); Console.WriteLine($"Resistance: {CalculateTotalResistanceForce(),6:F1} N"); Console.WriteLine($"Drag: {CalculateDragForce(),6:F1} N"); Console.WriteLine($"Rolling: {CalculateRollingResistanceForce(),6:F1} N"); diff --git a/Car simulation/Drivetrain.cs b/Car simulation/Drivetrain.cs index fc71698..49dd748 100644 --- a/Car simulation/Drivetrain.cs +++ b/Car simulation/Drivetrain.cs @@ -9,188 +9,139 @@ private int currentGear = 1; public float[] GearRatios { get; set; } = { - 3.8f, // 1st - Lower for better launch + 3.8f, // 1st 2.5f, // 2nd 1.8f, // 3rd 1.3f, // 4th - 1.0f, // 5th - Direct drive - 0.8f, // 6th - Overdrive - 0.65f // 7th - Double overdrive (optional) + 1.0f, // 5th + 0.8f, // 6th + 0.65f // 7th }; - public float FinalDriveRatio { get; set; } = 5.0f; + public float FinalDriveRatio { get; set; } = 4.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 + public float MaxClutchTorque { get; set; } = 450f; + public float ClutchStiffness { get; set; } = 20f; // Softer spring // 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 float ClutchSlipRatio { get; private set; } 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) + public void Update(float deltaTime) { if (ClutchEngagement <= 0.01f || TotalRatio == 0) { ClutchTorque = 0; TransmittedPower = 0; + ClutchSlipRatio = 1f; return; } - CalculateSpeedDifference(); - float clutchTorque = CalculateClutchTorque(); + // Calculate expected vs actual wheel speeds + float expectedWheelOmega = Engine.AngularVelocity / TotalRatio; + float actualWheelOmega = WheelSystem.AngularVelocity; + float omegaDifference = actualWheelOmega - expectedWheelOmega; - bool engineDrivingWheels = clutchTorque > 0; - bool wheelsDrivingEngine = clutchTorque < 0; + // Calculate max torque clutch can transmit + float maxClutchTorque = MaxClutchTorque * ClutchEngagement; - if (engineDrivingWheels) + // Simple spring model: torque tries to sync speeds + float desiredTorque = -omegaDifference * ClutchStiffness; + + // Clamp to clutch capacity + desiredTorque = Math.Clamp(desiredTorque, -maxClutchTorque, maxClutchTorque); + + // Also limit by engine capability when accelerating + if (desiredTorque > 0) { - // Engine -> Wheels (normal driving) - ApplyEngineToWheels(clutchTorque, deltaTime); - } - else if (wheelsDrivingEngine) - { - // Wheels -> Engine (engine braking) - ApplyWheelsToEngine(clutchTorque, deltaTime); + float engineTorque = Engine.GetTorqueOutput() * Engine.GetActualThrottle(); + float maxEngineTorqueAtWheels = engineTorque * TotalRatio * Efficiency; + desiredTorque = Math.Min(desiredTorque, maxEngineTorqueAtWheels); } - TransmittedPower = clutchTorque * SpeedDifference; + ClutchTorque = desiredTorque; + + // Calculate energy transfer based on torque + float energyTransferred = 0f; + + if (omegaDifference > 0.01f) // Wheels → Engine (engine braking) + { + // Power = torque × angular velocity (at slower side - engine) + float power = ClutchTorque * (Engine.AngularVelocity); + energyTransferred = power * deltaTime; + + // Wheels lose energy, engine gains (minus efficiency losses) + float wheelEnergyLoss = Math.Abs(energyTransferred); + float engineEnergyGain = wheelEnergyLoss * Efficiency; + + WheelSystem.TotalEnergy -= wheelEnergyLoss; + Engine.FlywheelEnergy += engineEnergyGain; + } + else if (omegaDifference < -0.01f) // Engine → Wheels (acceleration) + { + // Power = torque × angular velocity (at faster side - engine) + float power = -ClutchTorque * Engine.AngularVelocity; // Negative torque, positive power + energyTransferred = power * deltaTime; + + // Engine loses energy, wheels gain + float engineEnergyLoss = Math.Abs(energyTransferred); + float wheelEnergyGain = engineEnergyLoss * Efficiency; + + Engine.FlywheelEnergy -= engineEnergyLoss; + WheelSystem.TotalEnergy += wheelEnergyGain; + } + else + { + // Nearly synchronized + energyTransferred = 0; + } + + // Calculate transmitted power + TransmittedPower = energyTransferred / deltaTime; + + // Calculate clutch slip CORRECTLY: + // Slip = 0 when torque < max torque (clutch can handle it) + // Slip = 1 when torque = max torque (clutch is slipping) + if (maxClutchTorque > 0) + { + float torqueRatio = Math.Abs(ClutchTorque) / maxClutchTorque; + // If we're transmitting max torque, clutch is slipping + // If we're transmitting less, clutch is gripping + ClutchSlipRatio = torqueRatio; // 0 = no slip, 1 = full slip + } + else + { + ClutchSlipRatio = 1f; + } } - private void ApplyEngineToWheels(float clutchTorque, float deltaTime) + // Other methods... + public float GearRatio => GetCurrentGearRatio(); + public float TotalRatio => GearRatio * FinalDriveRatio; + + private float GetCurrentGearRatio() { - // 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); + if (currentGear == 0) return 0f; + if (currentGear == -1) return -3.5f; + if (currentGear > 0 && currentGear <= GearRatios.Length) + return GearRatios[currentGear - 1]; + return 0f; } - 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; + float expectedWheelOmega = Engine.AngularVelocity / TotalRatio; + float actualWheelOmega = WheelSystem.AngularVelocity; + return (actualWheelOmega - expectedWheelOmega) * PhysicsUtil.RAD_PER_SEC_TO_RPM; } public string GetCurrentGearName() @@ -202,5 +153,13 @@ _ => currentGear.ToString() }; } + + public float GetClutchSlipPercent() + { + return ClutchSlipRatio * 100f; + } + + public void GearUp() { if (currentGear < GearRatios.Length) currentGear++; } + public void GearDown() { if (currentGear > 1) currentGear--; } } } \ No newline at end of file diff --git a/Car simulation/Engine.cs b/Car simulation/Engine.cs index 9308aee..58e024a 100644 --- a/Car simulation/Engine.cs +++ b/Car simulation/Engine.cs @@ -3,7 +3,7 @@ public class Engine { // Energy state - public float FlywheelEnergy { get; set; } // Joules + public float FlywheelEnergy { get; set; } // Values public float RPM => GetRPM(); @@ -11,40 +11,35 @@ public float CurrentPower { get; private set; } // Physical properties - public float MomentOfInertia { get; set; } = 0.25f; // kg·m² + public float MomentOfInertia { get; set; } = 0.25f; 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 + // Torque curve public Dictionary TorqueCurve { get; set; } = new() { - // RPM - Torque Nm { 0f, 0f }, - { 800f, 150f }, // Idle - { 2000f, 200f }, // Peak torque + { 800f, 150f }, + { 2000f, 250 }, { 4500f, 250f }, - { 7200f, 250f }, - { 9200f, 250f }, - { 10000f, 200f }, - { 11000f, 0f } + { 6800f, 200f }, + { 7200f, 150 }, + { 7500f, 0f }, }; public Engine() { - // Start with idle energy FlywheelEnergy = GetEnergyFromRPM(IdleRPM); } - // Calculations - - public float CalculateFrictionEnergy(float deltaTime) + public float CalculateFrictionLoss(float deltaTime) { - // Real friction torque data for 2.0L engine (Nm) float frictionTorque; - if (RPM < 500) frictionTorque = 15f; // Static/breakaway + // Realistic friction based on RPM + if (RPM < 500) frictionTorque = 15f; else if (RPM < 1000) frictionTorque = 14f; else if (RPM < 2000) frictionTorque = 16f; else if (RPM < 3000) frictionTorque = 18f; @@ -58,29 +53,28 @@ return frictionPower * deltaTime; } - private float CalculateCombustionEnergy(float deltaTime) + public float CalculateCombustionPower(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); + // Idle control: maintain idle speed when throttle is low + if (RPM < IdleRPM && Throttle < 0.1f) + { + float idleThrottle = (IdleRPM - RPM) / 200f; + return Math.Clamp(idleThrottle, 0.1f, 0.3f); + } + + return Throttle; } public float GetOmega() { if (FlywheelEnergy <= 0) return 0; - return MathF.Sqrt(2f * FlywheelEnergy / MomentOfInertia); + return MathF.Sqrt(2f * FlywheelEnergy / MomentOfInertia); } public float GetRPM() @@ -88,15 +82,12 @@ 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; @@ -118,25 +109,28 @@ return 0f; } - public void ApplyTorque(float torque, float deltaTime) + public void Update(float deltaTime) { - if (torque == 0) return; + // Combustion adds energy (if throttle > 0) + float combustionEnergy = CalculateCombustionPower(deltaTime); - float work = torque * AngularVelocity * deltaTime; + // Friction always removes energy + float frictionLoss = CalculateFrictionLoss(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; + // Net energy change from combustion and friction ONLY + // Note: Drivetrain energy transfer happens separately in Drivetrain.Update() + float netEnergy = combustionEnergy - frictionLoss; CurrentPower = netEnergy / deltaTime; + FlywheelEnergy += netEnergy; + + // Stall protection - keep engine running if it has throttle + float stallEnergy = GetEnergyFromRPM(StallSpeed); + if (FlywheelEnergy < stallEnergy && Throttle > 0.1f) + { + FlywheelEnergy = stallEnergy * 1.2f; + } + FlywheelEnergy = Math.Max(FlywheelEnergy, 0); } } diff --git a/Car simulation/Program.cs b/Car simulation/Program.cs index 68a4770..733ee76 100644 --- a/Car simulation/Program.cs +++ b/Car simulation/Program.cs @@ -138,21 +138,21 @@ internal class Program // brake if (IsKeyDown(Keyboard.Key.B)) { - car.BrakeInput = Math.Min(car.BrakeInput + 0.5f * deltaTime, 1.0f); + car.BrakeInput = Math.Min(car.BrakeInput + 1f * deltaTime, 1.0f); } else { - car.BrakeInput = Math.Max(car.BrakeInput - 1f * deltaTime, 0f); + car.BrakeInput = Math.Max(car.BrakeInput - 4f * deltaTime, 0f); } // clutch if (IsKeyDown(Keyboard.Key.Up)) { - car.ClutchInput = Math.Min(car.ClutchInput + 1f * deltaTime, 1.0f); + car.ClutchInput = Math.Min(car.ClutchInput + 0.1f * deltaTime, 1.0f); } else if (IsKeyDown(Keyboard.Key.Down)) { - car.ClutchInput = Math.Max(car.ClutchInput - 1f * deltaTime, 0f); + car.ClutchInput = Math.Max(car.ClutchInput - 0.1f * deltaTime, 0f); } // clutch diff --git a/Car simulation/WheelSystem.cs b/Car simulation/WheelSystem.cs index 2f1d710..c3393c1 100644 --- a/Car simulation/WheelSystem.cs +++ b/Car simulation/WheelSystem.cs @@ -4,27 +4,43 @@ { // Physical properties public float Radius { get; set; } = 0.3f; // meters - public float Inertia { get; set; } = 2.0f; // kg·m² per wheel + public float WheelInertia { get; set; } = 2.0f; // kg·m² per wheel + public float CarMass { get; set; } = 1500f; // kg - Car mass integrated into wheel system + public int WheelCount { get; set; } = 4; public int DrivenWheels { get; set; } = 2; // 2WD // State - public float WheelEnergy { get; set; } = 0f; // Joules + public float TotalEnergy { get; set; } = 0f; // Joules (rotational + translational) public float AngularVelocity => GetOmega(); public float RPM => GetRPM(); - public float Speed => GetSpeed(); + public float CarSpeed => GetCarSpeed(); // Now returns actual car speed + public float ResistanceTorque { get; set; } = 0f; // Calculations + public float GetTotalRotationalInertia() + { + return WheelInertia * WheelCount; + } + + public float GetEquivalentCarInertia() + { + // Convert car mass to equivalent rotational inertia at wheels + // I = m * r² (from v = ω * r, so KE_translational = 0.5 * m * v² = 0.5 * m * (ωr)² = 0.5 * m * r² * ω²) + return CarMass * Radius * Radius; + } + public float GetTotalInertia() { - return Inertia * WheelCount; + // Total inertia = rotational inertia of wheels + equivalent inertia of car mass + return GetTotalRotationalInertia() + GetEquivalentCarInertia(); } public float GetOmega() { - if (WheelEnergy <= 0 || GetTotalInertia() <= 0) return 0f; - return MathF.Sqrt(2f * WheelEnergy / GetTotalInertia()); + if (TotalEnergy <= 0 || GetTotalInertia() <= 0) return 0f; + return MathF.Sqrt(2f * TotalEnergy / GetTotalInertia()); } public float GetRPM() @@ -32,27 +48,46 @@ return AngularVelocity * PhysicsUtil.RAD_PER_SEC_TO_RPM; } - public float GetSpeed() + public float GetCarSpeed() { + // v = ω * r (no slip assumed for base calculation) return AngularVelocity * Radius; } + public float GetRotationalEnergy() + { + // Just the energy from wheel rotation + float omega = GetOmega(); + return 0.5f * GetTotalRotationalInertia() * omega * omega; + } + + public float GetTranslationalEnergy() + { + // Just the energy from car motion + float speed = GetCarSpeed(); + return 0.5f * CarMass * speed * speed; + } + public float GetEnergyFromSpeed(float speed) { + // Calculate total energy for given car speed + // Total energy = rotational energy of wheels + translational energy of car float omega = speed / Radius; - return 0.5f * GetTotalInertia() * omega * omega; + float rotationalEnergy = 0.5f * GetTotalRotationalInertia() * omega * omega; + float translationalEnergy = 0.5f * CarMass * speed * speed; + return rotationalEnergy + translationalEnergy; } public void SetSpeed(float speed) { - WheelEnergy = GetEnergyFromSpeed(speed); + TotalEnergy = GetEnergyFromSpeed(speed); } - // Apply work to the wheels + // Apply work to the entire system (wheels + car) public void ApplyWork(float work) { - WheelEnergy += work; - WheelEnergy = Math.Max(WheelEnergy, 0); + TotalEnergy += work; + TotalEnergy = Math.Max(TotalEnergy, 0); } public void ApplyTorque(float torque, float deltaTime) @@ -70,8 +105,7 @@ 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 + // Static friction - return without applying resistance to allow startup return; } @@ -86,7 +120,7 @@ } float energyNew = 0.5f * GetTotalInertia() * omegaNew * omegaNew; - WheelEnergy = Math.Max(energyNew, 0); + TotalEnergy = Math.Max(energyNew, 0); } } } \ No newline at end of file