Energy based changes
This commit is contained in:
@@ -5,10 +5,11 @@ namespace Car_simulation
|
|||||||
public class Car
|
public class Car
|
||||||
{
|
{
|
||||||
public Vector2 Position = new Vector2(0, 0);
|
public Vector2 Position = new Vector2(0, 0);
|
||||||
public Vector2 Velocity = new Vector2(0, 0);
|
public Vector2 Velocity => new Vector2(WheelSystem.CarSpeed, 0); // Now directly from wheels
|
||||||
public float Speed => Velocity.Length;
|
|
||||||
|
|
||||||
public float Mass = 1500f; // kg
|
public float Speed => WheelSystem.CarSpeed;
|
||||||
|
|
||||||
|
public float Mass { get; set; } = 1500f; // kg
|
||||||
public int WheelCount = 4;
|
public int WheelCount = 4;
|
||||||
public int DrivenWheels = 2;
|
public int DrivenWheels = 2;
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ namespace Car_simulation
|
|||||||
|
|
||||||
// Aerodynamics
|
// Aerodynamics
|
||||||
private const float AirDensity = 1.225f;
|
private const float AirDensity = 1.225f;
|
||||||
public float DragCoefficient = 0.1f;
|
public float DragCoefficient = 0.3f;
|
||||||
public float FrontalArea = 2.2f; // m²
|
public float FrontalArea = 2.2f; // m²
|
||||||
public float RollingResistanceCoefficient = 0.015f;
|
public float RollingResistanceCoefficient = 0.015f;
|
||||||
|
|
||||||
@@ -36,12 +37,14 @@ namespace Car_simulation
|
|||||||
{
|
{
|
||||||
Engine = new Engine();
|
Engine = new Engine();
|
||||||
WheelSystem = new WheelSystem();
|
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.WheelCount = WheelCount;
|
||||||
WheelSystem.DrivenWheels = DrivenWheels;
|
WheelSystem.DrivenWheels = DrivenWheels;
|
||||||
|
|
||||||
|
Drivetrain = new Drivetrain(Engine, WheelSystem);
|
||||||
|
|
||||||
InitializeAudio();
|
InitializeAudio();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +55,6 @@ namespace Car_simulation
|
|||||||
_engineSound = new EngineSound();
|
_engineSound = new EngineSound();
|
||||||
_engineSound.SetEngineState(Engine.IdleRPM, 0f);
|
_engineSound.SetEngineState(Engine.IdleRPM, 0f);
|
||||||
_engineSound.StartSound();
|
_engineSound.StartSound();
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -64,28 +66,31 @@ namespace Car_simulation
|
|||||||
public void Update(float deltaTime)
|
public void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
Engine.Throttle = ThrottleInput;
|
Engine.Throttle = ThrottleInput;
|
||||||
Drivetrain.ClutchEngagement = 1f - ClutchInput; // Convert: 0 input = 1 engagement
|
Drivetrain.ClutchEngagement = 1f - ClutchInput;
|
||||||
|
|
||||||
if (ForceClutch)
|
if (ForceClutch)
|
||||||
Drivetrain.ClutchEngagement = 0f;
|
Drivetrain.ClutchEngagement = 0f;
|
||||||
|
|
||||||
float resistanceTorque = CalculateResistanceTorque();
|
// Update engine
|
||||||
WheelSystem.ResistanceTorque = resistanceTorque;
|
Engine.Update(deltaTime);
|
||||||
|
|
||||||
|
// Update drivetrain (transfers energy between engine and wheels+car)
|
||||||
Drivetrain.Update(deltaTime);
|
Drivetrain.Update(deltaTime);
|
||||||
|
|
||||||
|
// Calculate and apply resistance
|
||||||
|
float resistanceForce = CalculateTotalResistanceForce();
|
||||||
|
WheelSystem.ResistanceTorque = resistanceForce * WheelSystem.Radius;
|
||||||
WheelSystem.ApplyResistance(deltaTime);
|
WheelSystem.ApplyResistance(deltaTime);
|
||||||
|
|
||||||
float engineLoad = Drivetrain.CalculateEngineLoad(deltaTime);
|
// Apply braking
|
||||||
Engine.Update(deltaTime, engineLoad);
|
|
||||||
|
|
||||||
UpdateVehicleMotion(deltaTime);
|
|
||||||
ApplyBraking(deltaTime);
|
ApplyBraking(deltaTime);
|
||||||
|
|
||||||
|
// Update position based on velocity (which comes from WheelSystem)
|
||||||
|
Position += Velocity * deltaTime;
|
||||||
|
|
||||||
if (_audioEnabled)
|
if (_audioEnabled)
|
||||||
{
|
|
||||||
UpdateAudio();
|
UpdateAudio();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void 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)
|
private void ApplyBraking(float deltaTime)
|
||||||
{
|
{
|
||||||
if (BrakeInput <= 0) return;
|
if (BrakeInput <= 0) return;
|
||||||
|
|
||||||
float brakeTorque = BrakeInput * 500f; // 500 Nm max brake torque
|
float brakeTorque = BrakeInput * 3000f;
|
||||||
|
|
||||||
WheelSystem.ApplyTorque(-brakeTorque, deltaTime);
|
WheelSystem.ApplyTorque(-brakeTorque, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,40 +122,34 @@ namespace Car_simulation
|
|||||||
|
|
||||||
private float CalculateDragForce()
|
private float CalculateDragForce()
|
||||||
{
|
{
|
||||||
// F_drag = 0.5 * ρ * Cd * A * v²
|
|
||||||
float speed = Speed;
|
float speed = Speed;
|
||||||
return 0.5f * AirDensity * DragCoefficient * FrontalArea * speed * speed;
|
return 0.5f * AirDensity * DragCoefficient * FrontalArea * speed * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float CalculateRollingResistanceForce()
|
private float CalculateRollingResistanceForce()
|
||||||
{
|
{
|
||||||
// F_rolling = C_r * m * g
|
|
||||||
return RollingResistanceCoefficient * Mass * 9.81f;
|
return RollingResistanceCoefficient * Mass * 9.81f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert resistance force to wheel torque
|
|
||||||
public float CalculateResistanceTorque()
|
|
||||||
{
|
|
||||||
float totalForce = CalculateTotalResistanceForce();
|
|
||||||
return totalForce * WheelSystem.Radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisplayUpdate()
|
public void DisplayUpdate()
|
||||||
{
|
{
|
||||||
Console.SetCursorPosition(0, 0);
|
Console.SetCursorPosition(0, 0);
|
||||||
Console.WriteLine($"Engine Energy: {Engine.FlywheelEnergy,7:F0} J");
|
Console.WriteLine($"Engine Energy: {Engine.FlywheelEnergy,7:F0} J");
|
||||||
Console.WriteLine($"Engine Torque: {Engine.GetTorqueOutput(),7:F0} Nm");
|
Console.WriteLine($"Engine Torque: {Engine.GetTorqueOutput(),7:F0} Nm");
|
||||||
Console.WriteLine($"Engine RPM: {Engine.RPM,7:F0}");
|
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($"Wheel RPM: {WheelSystem.RPM,7:F0}");
|
||||||
Console.WriteLine($"Vehicle: {Speed * 3.6f,7:F1} km/h");
|
Console.WriteLine($"Vehicle: {Speed * 3.6f,7:F1} km/h");
|
||||||
Console.WriteLine($"Throttle: {Engine.GetActualThrottle() * 100,6:F1}%");
|
Console.WriteLine($"Throttle: {Engine.GetActualThrottle() * 100,6:F1}%");
|
||||||
Console.WriteLine($"Power: {Engine.CurrentPower / 1000,6:F1} kW");
|
Console.WriteLine($"Power: {Engine.CurrentPower / 1000,6:F1} kW");
|
||||||
Console.WriteLine($"Transmitted: {Drivetrain.TransmittedPower / 1000,6:F1} kW");
|
Console.WriteLine($"Transmitted: {Drivetrain.TransmittedPower / 1000,6:F1} kW");
|
||||||
Console.WriteLine($"Brake: {BrakeInput * 100,6:F1}%");
|
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($"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 T: {Drivetrain.ClutchTorque,6:F0} Nm");
|
||||||
|
Console.WriteLine($"Clutch Slip: {Drivetrain.GetClutchSlipPercent(),6:F1}%");
|
||||||
Console.WriteLine($"Resistance: {CalculateTotalResistanceForce(),6:F1} N");
|
Console.WriteLine($"Resistance: {CalculateTotalResistanceForce(),6:F1} N");
|
||||||
Console.WriteLine($"Drag: {CalculateDragForce(),6:F1} N");
|
Console.WriteLine($"Drag: {CalculateDragForce(),6:F1} N");
|
||||||
Console.WriteLine($"Rolling: {CalculateRollingResistanceForce(),6:F1} N");
|
Console.WriteLine($"Rolling: {CalculateRollingResistanceForce(),6:F1} N");
|
||||||
|
|||||||
@@ -9,188 +9,139 @@
|
|||||||
private int currentGear = 1;
|
private int currentGear = 1;
|
||||||
public float[] GearRatios { get; set; } =
|
public float[] GearRatios { get; set; } =
|
||||||
{
|
{
|
||||||
3.8f, // 1st - Lower for better launch
|
3.8f, // 1st
|
||||||
2.5f, // 2nd
|
2.5f, // 2nd
|
||||||
1.8f, // 3rd
|
1.8f, // 3rd
|
||||||
1.3f, // 4th
|
1.3f, // 4th
|
||||||
1.0f, // 5th - Direct drive
|
1.0f, // 5th
|
||||||
0.8f, // 6th - Overdrive
|
0.8f, // 6th
|
||||||
0.65f // 7th - Double overdrive (optional)
|
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 Efficiency { get; set; } = 0.95f;
|
||||||
public float ClutchEngagement { get; set; } = 0f; // 0 = disengaged, 1 = fully engaged
|
public float ClutchEngagement { get; set; } = 0f; // 0 = disengaged, 1 = fully engaged
|
||||||
|
|
||||||
// Calculated
|
|
||||||
public float GearRatio => GetCurrentGearRatio();
|
|
||||||
public float TotalRatio => GearRatio * FinalDriveRatio;
|
|
||||||
|
|
||||||
// Clutch properties
|
// Clutch properties
|
||||||
public float ClutchStiffness { get; set; } = 500f; // Nm/(rad/s) - how strongly clutch pulls speeds together
|
public float MaxClutchTorque { get; set; } = 450f;
|
||||||
public float MaxClutchTorque { get; set; } = 4500f; // Maximum torque clutch can transmit
|
public float ClutchStiffness { get; set; } = 20f; // Softer spring
|
||||||
|
|
||||||
// State
|
// State
|
||||||
public float SpeedDifference { get; private set; } // rad/s
|
|
||||||
public float ClutchTorque { get; private set; }
|
public float ClutchTorque { get; private set; }
|
||||||
public float TransmittedPower { get; private set; }
|
public float TransmittedPower { get; private set; }
|
||||||
|
public float ClutchSlipRatio { get; private set; }
|
||||||
private float previousWheelOmega = 0f;
|
|
||||||
|
|
||||||
public Drivetrain(Engine engine, WheelSystem wheelSystem)
|
public Drivetrain(Engine engine, WheelSystem wheelSystem)
|
||||||
{
|
{
|
||||||
Engine = engine;
|
Engine = engine;
|
||||||
WheelSystem = wheelSystem;
|
WheelSystem = wheelSystem;
|
||||||
previousWheelOmega = wheelSystem.AngularVelocity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GearUp()
|
public void Update(float deltaTime)
|
||||||
{
|
|
||||||
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)
|
if (ClutchEngagement <= 0.01f || TotalRatio == 0)
|
||||||
{
|
{
|
||||||
ClutchTorque = 0;
|
ClutchTorque = 0;
|
||||||
TransmittedPower = 0;
|
TransmittedPower = 0;
|
||||||
|
ClutchSlipRatio = 1f;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CalculateSpeedDifference();
|
// Calculate expected vs actual wheel speeds
|
||||||
float clutchTorque = CalculateClutchTorque();
|
float expectedWheelOmega = Engine.AngularVelocity / TotalRatio;
|
||||||
|
float actualWheelOmega = WheelSystem.AngularVelocity;
|
||||||
|
float omegaDifference = actualWheelOmega - expectedWheelOmega;
|
||||||
|
|
||||||
bool engineDrivingWheels = clutchTorque > 0;
|
// Calculate max torque clutch can transmit
|
||||||
bool wheelsDrivingEngine = clutchTorque < 0;
|
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)
|
float engineTorque = Engine.GetTorqueOutput() * Engine.GetActualThrottle();
|
||||||
ApplyEngineToWheels(clutchTorque, deltaTime);
|
float maxEngineTorqueAtWheels = engineTorque * TotalRatio * Efficiency;
|
||||||
|
desiredTorque = Math.Min(desiredTorque, maxEngineTorqueAtWheels);
|
||||||
}
|
}
|
||||||
else if (wheelsDrivingEngine)
|
|
||||||
|
ClutchTorque = desiredTorque;
|
||||||
|
|
||||||
|
// Calculate energy transfer based on torque
|
||||||
|
float energyTransferred = 0f;
|
||||||
|
|
||||||
|
if (omegaDifference > 0.01f) // Wheels → Engine (engine braking)
|
||||||
{
|
{
|
||||||
// Wheels -> Engine (engine braking)
|
// Power = torque × angular velocity (at slower side - engine)
|
||||||
ApplyWheelsToEngine(clutchTorque, deltaTime);
|
float power = ClutchTorque * (Engine.AngularVelocity);
|
||||||
}
|
energyTransferred = power * deltaTime;
|
||||||
|
|
||||||
TransmittedPower = clutchTorque * SpeedDifference;
|
// Wheels lose energy, engine gains (minus efficiency losses)
|
||||||
}
|
float wheelEnergyLoss = Math.Abs(energyTransferred);
|
||||||
|
float engineEnergyGain = wheelEnergyLoss * Efficiency;
|
||||||
|
|
||||||
private void ApplyEngineToWheels(float clutchTorque, float deltaTime)
|
WheelSystem.TotalEnergy -= wheelEnergyLoss;
|
||||||
|
Engine.FlywheelEnergy += engineEnergyGain;
|
||||||
|
}
|
||||||
|
else if (omegaDifference < -0.01f) // Engine → Wheels (acceleration)
|
||||||
{
|
{
|
||||||
// Existing logic for engine driving wheels
|
// Power = torque × angular velocity (at faster side - engine)
|
||||||
float netWheelTorque = clutchTorque * Efficiency - WheelSystem.ResistanceTorque;
|
float power = -ClutchTorque * Engine.AngularVelocity; // Negative torque, positive power
|
||||||
float netEngineTorque = -clutchTorque / TotalRatio;
|
energyTransferred = power * deltaTime;
|
||||||
|
|
||||||
// Apply to both
|
// Engine loses energy, wheels gain
|
||||||
Engine.ApplyTorque(netEngineTorque, deltaTime);
|
float engineEnergyLoss = Math.Abs(energyTransferred);
|
||||||
WheelSystem.ApplyTorque(netWheelTorque, deltaTime);
|
float wheelEnergyGain = engineEnergyLoss * Efficiency;
|
||||||
|
|
||||||
|
Engine.FlywheelEnergy -= engineEnergyLoss;
|
||||||
|
WheelSystem.TotalEnergy += wheelEnergyGain;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void ApplyWheelsToEngine(float clutchTorque, float deltaTime)
|
|
||||||
{
|
{
|
||||||
// Wheels driving engine (engine braking)
|
// Nearly synchronized
|
||||||
// Negative clutchTorque means wheels are trying to spin engine faster
|
energyTransferred = 0;
|
||||||
|
|
||||||
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()
|
// 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 wheelInertia = WheelSystem.GetTotalInertia();
|
float torqueRatio = Math.Abs(ClutchTorque) / maxClutchTorque;
|
||||||
return Engine.MomentOfInertia + (wheelInertia * TotalRatio * TotalRatio);
|
// 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
|
||||||
public float CalculateEngineLoad(float deltaTime)
|
|
||||||
{
|
{
|
||||||
if (ClutchEngagement <= 0.01f) return 0f;
|
ClutchSlipRatio = 1f;
|
||||||
|
}
|
||||||
float wheelResistanceTorque = WheelSystem.ResistanceTorque;
|
|
||||||
float engineLoadTorque = wheelResistanceTorque / (TotalRatio * Efficiency);
|
|
||||||
|
|
||||||
float inertiaLoad = CalculateInertiaLoad(deltaTime);
|
|
||||||
|
|
||||||
return engineLoadTorque + inertiaLoad;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float CalculateInertiaLoad(float deltaTime)
|
// Other methods...
|
||||||
|
public float GearRatio => GetCurrentGearRatio();
|
||||||
|
public float TotalRatio => GearRatio * FinalDriveRatio;
|
||||||
|
|
||||||
|
private float GetCurrentGearRatio()
|
||||||
{
|
{
|
||||||
float wheelAlpha = (WheelSystem.AngularVelocity - previousWheelOmega) / deltaTime;
|
if (currentGear == 0) return 0f;
|
||||||
previousWheelOmega = WheelSystem.AngularVelocity;
|
if (currentGear == -1) return -3.5f;
|
||||||
|
if (currentGear > 0 && currentGear <= GearRatios.Length)
|
||||||
float inertiaTorque = wheelAlpha * WheelSystem.GetTotalInertia();
|
return GearRatios[currentGear - 1];
|
||||||
return inertiaTorque / (TotalRatio * TotalRatio * Efficiency);
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(float deltaTime)
|
|
||||||
{
|
|
||||||
ApplyDrivetrainWork(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper methods
|
|
||||||
public float GetSpeedDifferenceRPM()
|
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()
|
public string GetCurrentGearName()
|
||||||
@@ -202,5 +153,13 @@
|
|||||||
_ => currentGear.ToString()
|
_ => currentGear.ToString()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float GetClutchSlipPercent()
|
||||||
|
{
|
||||||
|
return ClutchSlipRatio * 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GearUp() { if (currentGear < GearRatios.Length) currentGear++; }
|
||||||
|
public void GearDown() { if (currentGear > 1) currentGear--; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
public class Engine
|
public class Engine
|
||||||
{
|
{
|
||||||
// Energy state
|
// Energy state
|
||||||
public float FlywheelEnergy { get; set; } // Joules
|
public float FlywheelEnergy { get; set; }
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
public float RPM => GetRPM();
|
public float RPM => GetRPM();
|
||||||
@@ -11,40 +11,35 @@
|
|||||||
public float CurrentPower { get; private set; }
|
public float CurrentPower { get; private set; }
|
||||||
|
|
||||||
// Physical properties
|
// 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 IdleRPM { get; set; } = 800f;
|
||||||
public float StallSpeed { get; set; } = 200f;
|
public float StallSpeed { get; set; } = 200f;
|
||||||
public float Throttle { get; set; } = 0f;
|
public float Throttle { get; set; } = 0f;
|
||||||
public bool IsRunning => RPM > StallSpeed;
|
public bool IsRunning => RPM > StallSpeed;
|
||||||
|
|
||||||
// Torque characteristics
|
// Torque curve
|
||||||
public Dictionary<float, float> TorqueCurve { get; set; } = new()
|
public Dictionary<float, float> TorqueCurve { get; set; } = new()
|
||||||
{
|
{
|
||||||
// RPM - Torque Nm
|
|
||||||
{ 0f, 0f },
|
{ 0f, 0f },
|
||||||
{ 800f, 150f }, // Idle
|
{ 800f, 150f },
|
||||||
{ 2000f, 200f }, // Peak torque
|
{ 2000f, 250 },
|
||||||
{ 4500f, 250f },
|
{ 4500f, 250f },
|
||||||
{ 7200f, 250f },
|
{ 6800f, 200f },
|
||||||
{ 9200f, 250f },
|
{ 7200f, 150 },
|
||||||
{ 10000f, 200f },
|
{ 7500f, 0f },
|
||||||
{ 11000f, 0f }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public Engine()
|
public Engine()
|
||||||
{
|
{
|
||||||
// Start with idle energy
|
|
||||||
FlywheelEnergy = GetEnergyFromRPM(IdleRPM);
|
FlywheelEnergy = GetEnergyFromRPM(IdleRPM);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculations
|
public float CalculateFrictionLoss(float deltaTime)
|
||||||
|
|
||||||
public float CalculateFrictionEnergy(float deltaTime)
|
|
||||||
{
|
{
|
||||||
// Real friction torque data for 2.0L engine (Nm)
|
|
||||||
float frictionTorque;
|
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 < 1000) frictionTorque = 14f;
|
||||||
else if (RPM < 2000) frictionTorque = 16f;
|
else if (RPM < 2000) frictionTorque = 16f;
|
||||||
else if (RPM < 3000) frictionTorque = 18f;
|
else if (RPM < 3000) frictionTorque = 18f;
|
||||||
@@ -58,23 +53,22 @@
|
|||||||
return frictionPower * deltaTime;
|
return frictionPower * deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float CalculateCombustionEnergy(float deltaTime)
|
public float CalculateCombustionPower(float deltaTime)
|
||||||
{
|
{
|
||||||
float torque = GetTorqueOutput() * GetActualThrottle();
|
float torque = GetTorqueOutput() * GetActualThrottle();
|
||||||
return torque * AngularVelocity * deltaTime;
|
return torque * AngularVelocity * deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float CalculateLoadEnergy(float deltaTime, float loadTorque)
|
|
||||||
{
|
|
||||||
return loadTorque * AngularVelocity * deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get
|
|
||||||
|
|
||||||
public float GetActualThrottle()
|
public float GetActualThrottle()
|
||||||
{
|
{
|
||||||
float idleThrottle = Math.Max((IdleRPM - RPM) / 10, 0);
|
// Idle control: maintain idle speed when throttle is low
|
||||||
return Math.Clamp(Throttle + idleThrottle, 0, 1);
|
if (RPM < IdleRPM && Throttle < 0.1f)
|
||||||
|
{
|
||||||
|
float idleThrottle = (IdleRPM - RPM) / 200f;
|
||||||
|
return Math.Clamp(idleThrottle, 0.1f, 0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Throttle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetOmega()
|
public float GetOmega()
|
||||||
@@ -88,15 +82,12 @@
|
|||||||
return GetOmega() * PhysicsUtil.RAD_PER_SEC_TO_RPM;
|
return GetOmega() * PhysicsUtil.RAD_PER_SEC_TO_RPM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set
|
|
||||||
|
|
||||||
public float GetEnergyFromRPM(float rpm)
|
public float GetEnergyFromRPM(float rpm)
|
||||||
{
|
{
|
||||||
float omega = rpm * PhysicsUtil.RPM_TO_RAD_PER_SEC;
|
float omega = rpm * PhysicsUtil.RPM_TO_RAD_PER_SEC;
|
||||||
return 0.5f * MomentOfInertia * omega * omega;
|
return 0.5f * MomentOfInertia * omega * omega;
|
||||||
}
|
}
|
||||||
|
|
||||||
// torque curve
|
|
||||||
public float GetTorqueOutput()
|
public float GetTorqueOutput()
|
||||||
{
|
{
|
||||||
if (RPM <= 0) return 0;
|
if (RPM <= 0) return 0;
|
||||||
@@ -118,25 +109,28 @@
|
|||||||
return 0f;
|
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;
|
// Net energy change from combustion and friction ONLY
|
||||||
FlywheelEnergy = Math.Max(FlywheelEnergy, 0);
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
FlywheelEnergy = Math.Max(FlywheelEnergy, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,21 +138,21 @@ internal class Program
|
|||||||
// brake
|
// brake
|
||||||
if (IsKeyDown(Keyboard.Key.B))
|
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
|
else
|
||||||
{
|
{
|
||||||
car.BrakeInput = Math.Max(car.BrakeInput - 1f * deltaTime, 0f);
|
car.BrakeInput = Math.Max(car.BrakeInput - 4f * deltaTime, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clutch
|
// clutch
|
||||||
if (IsKeyDown(Keyboard.Key.Up))
|
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))
|
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
|
// clutch
|
||||||
|
|||||||
@@ -4,27 +4,43 @@
|
|||||||
{
|
{
|
||||||
// Physical properties
|
// Physical properties
|
||||||
public float Radius { get; set; } = 0.3f; // meters
|
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 WheelCount { get; set; } = 4;
|
||||||
public int DrivenWheels { get; set; } = 2; // 2WD
|
public int DrivenWheels { get; set; } = 2; // 2WD
|
||||||
|
|
||||||
// State
|
// State
|
||||||
public float WheelEnergy { get; set; } = 0f; // Joules
|
public float TotalEnergy { get; set; } = 0f; // Joules (rotational + translational)
|
||||||
public float AngularVelocity => GetOmega();
|
public float AngularVelocity => GetOmega();
|
||||||
public float RPM => GetRPM();
|
public float RPM => GetRPM();
|
||||||
public float Speed => GetSpeed();
|
public float CarSpeed => GetCarSpeed(); // Now returns actual car speed
|
||||||
|
|
||||||
public float ResistanceTorque { get; set; } = 0f;
|
public float ResistanceTorque { get; set; } = 0f;
|
||||||
|
|
||||||
// Calculations
|
// 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()
|
public float GetTotalInertia()
|
||||||
{
|
{
|
||||||
return Inertia * WheelCount;
|
// Total inertia = rotational inertia of wheels + equivalent inertia of car mass
|
||||||
|
return GetTotalRotationalInertia() + GetEquivalentCarInertia();
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetOmega()
|
public float GetOmega()
|
||||||
{
|
{
|
||||||
if (WheelEnergy <= 0 || GetTotalInertia() <= 0) return 0f;
|
if (TotalEnergy <= 0 || GetTotalInertia() <= 0) return 0f;
|
||||||
return MathF.Sqrt(2f * WheelEnergy / GetTotalInertia());
|
return MathF.Sqrt(2f * TotalEnergy / GetTotalInertia());
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetRPM()
|
public float GetRPM()
|
||||||
@@ -32,27 +48,46 @@
|
|||||||
return AngularVelocity * PhysicsUtil.RAD_PER_SEC_TO_RPM;
|
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;
|
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)
|
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;
|
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)
|
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)
|
public void ApplyWork(float work)
|
||||||
{
|
{
|
||||||
WheelEnergy += work;
|
TotalEnergy += work;
|
||||||
WheelEnergy = Math.Max(WheelEnergy, 0);
|
TotalEnergy = Math.Max(TotalEnergy, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyTorque(float torque, float deltaTime)
|
public void ApplyTorque(float torque, float deltaTime)
|
||||||
@@ -70,8 +105,7 @@
|
|||||||
|
|
||||||
if (MathF.Abs(omega) < 0.1f)
|
if (MathF.Abs(omega) < 0.1f)
|
||||||
{
|
{
|
||||||
// Check if we have enough torque to overcome static friction
|
// Static friction - return without applying resistance to allow startup
|
||||||
// For now, just return without applying resistance to allow startup
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +120,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
float energyNew = 0.5f * GetTotalInertia() * omegaNew * omegaNew;
|
float energyNew = 0.5f * GetTotalInertia() * omegaNew * omegaNew;
|
||||||
WheelEnergy = Math.Max(energyNew, 0);
|
TotalEnergy = Math.Max(energyNew, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user