Improved UI

This commit is contained in:
max
2025-12-18 03:00:13 +01:00
parent e0af3aefe1
commit 6249499be2
5 changed files with 272 additions and 142 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<RootNamespace>Car_simulation</RootNamespace> <RootNamespace>Car_simulation</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
@@ -12,4 +12,10 @@
<PackageReference Include="SFML.Net" Version="2.6.1" /> <PackageReference Include="SFML.Net" Version="2.6.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="arial.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@@ -48,6 +48,33 @@ namespace Car_simulation
InitializeAudio(); InitializeAudio();
} }
public List<string> GetDisplayData()
{
return new List<string>
{
$"Engine Energy: {Engine.FlywheelEnergy,7:F0} J",
$"Engine Torque: {Engine.GetTorqueOutput(),7:F0} Nm",
$"Engine RPM: {Engine.RPM,7:F0}",
$"Total Energy: {WheelSystem.TotalEnergy,7:F0} J",
$" (Wheel Rot: {WheelSystem.GetRotationalEnergy(),7:F0} J)",
$" (Car Trans: {WheelSystem.GetTranslationalEnergy(),7:F0} J)",
$"Wheel RPM: {WheelSystem.RPM,7:F0}",
$"Vehicle: {Speed * 3.6f,7:F1} km/h",
$"Throttle: {Engine.GetActualThrottle() * 100,6:F1}%",
$"Power: {Engine.CurrentPower / 1000,6:F1} kW",
$"Transmitted: {Drivetrain.TransmittedPower / 1000,6:F1} kW",
$"Brake: {BrakeInput * 100,6:F1}%",
$"Speed Diff: {Drivetrain.GetSpeedDifferenceRPM(),6:F0} RPM",
$"Clutch: {ClutchInput * 100,6:F1}% disengaged",
$"Clutch T: {Drivetrain.ClutchTorque,6:F0} Nm",
$"Clutch Slip: {Drivetrain.GetClutchSlipPercent(),6:F1}%",
$"Resistance: {CalculateTotalResistanceForce(),6:F1} N",
$"Drag: {CalculateDragForce(),6:F1} N",
$"Rolling: {CalculateRollingResistanceForce(),6:F1} N",
$"Gear: {Drivetrain.GetCurrentGearName(),3} (Ratio: {Drivetrain.GearRatio:F2}:1)"
};
}
private void InitializeAudio() private void InitializeAudio()
{ {
try try
@@ -120,40 +147,15 @@ namespace Car_simulation
return dragForce + rollingForce; return dragForce + rollingForce;
} }
private float CalculateDragForce() public float CalculateDragForce()
{ {
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() public float CalculateRollingResistanceForce()
{ {
return RollingResistanceCoefficient * Mass * 9.81f; return RollingResistanceCoefficient * Mass * 9.81f;
} }
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($"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($"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");
Console.WriteLine($"Gear: {Drivetrain.GetCurrentGearName(),3} (Ratio: {Drivetrain.GearRatio:F2}:1)");
}
} }
} }

View File

@@ -9,7 +9,7 @@ namespace Car_simulation
// Audio properties - smaller buffer for less latency // Audio properties - smaller buffer for less latency
private const uint SAMPLE_RATE = 44100; private const uint SAMPLE_RATE = 44100;
private const ushort CHANNEL_COUNT = 2; // Stereo private const ushort CHANNEL_COUNT = 2; // Stereo
private const float BUFFER_DURATION = 0.01f; // 10ms instead of 50ms! private const float BUFFER_DURATION = 0.05f; // 10ms instead of 50ms!
// Engine sound properties - NO SMOOTHING for instant response // Engine sound properties - NO SMOOTHING for instant response
private volatile float _currentRPM = 800f; // volatile for thread safety private volatile float _currentRPM = 800f; // volatile for thread safety
@@ -44,8 +44,6 @@ namespace Car_simulation
{ {
_harmonicPhases[i] = (float)(_random.NextDouble() * 2 * Math.PI); _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 // CALL THIS FROM YOUR PHYSICS THREAD - INSTANT UPDATE

View File

@@ -2,7 +2,8 @@
using SFML.Window; using SFML.Window;
using SFML.Graphics; using SFML.Graphics;
using SFML.System; using SFML.System;
using System.Diagnostics; using System.Collections.Generic;
using System;
internal class Program internal class Program
{ {
@@ -10,6 +11,18 @@ internal class Program
private bool _isRunning = true; private bool _isRunning = true;
private RenderWindow _window; private RenderWindow _window;
private Font _font;
private List<Text> _displayTexts = new List<Text>();
private RectangleShape _tachometerBackground;
private RectangleShape _tachometerNeedle;
private RectangleShape _speedometerBackground;
private RectangleShape _speedometerNeedle;
// Colors
private Color _backgroundColor = new Color(20, 20, 30);
private Color _textColor = new Color(220, 220, 220);
private Color _highlightColor = new Color(0, 150, 255);
private Color _warningColor = new Color(255, 100, 100);
// Timing for physics // Timing for physics
private Clock _clock = new Clock(); private Clock _clock = new Clock();
@@ -28,7 +41,7 @@ internal class Program
private void Run() private void Run()
{ {
_window = new RenderWindow(new VideoMode(800, 600), "Car Simulation", Styles.Default); _window = new RenderWindow(new VideoMode(1000, 800), "Car Simulation", Styles.Default);
_window.SetVisible(true); _window.SetVisible(true);
_window.SetFramerateLimit(60); _window.SetFramerateLimit(60);
_window.SetKeyRepeatEnabled(false); _window.SetKeyRepeatEnabled(false);
@@ -37,6 +50,17 @@ internal class Program
_window.KeyPressed += OnKeyPressed; _window.KeyPressed += OnKeyPressed;
_window.KeyReleased += OnKeyReleased; _window.KeyReleased += OnKeyReleased;
// Load font
try
{
_font = new Font("arial.ttf");
}
catch
{
_font = new Font("C:/Windows/Fonts/arial.ttf");
}
InitializeDisplay();
InitializeTrackedKeys(); InitializeTrackedKeys();
_clock.Restart(); _clock.Restart();
@@ -54,14 +78,10 @@ internal class Program
car.Update(_timePerUpdate.AsSeconds()); car.Update(_timePerUpdate.AsSeconds());
_accumulatedTime -= _timePerUpdate; _accumulatedTime -= _timePerUpdate;
_updateCount++; _updateCount++;
if (_accumulatedTime >= Time.FromSeconds(0.2f))
{
_accumulatedTime = _timePerUpdate;
}
} }
UpdateDisplay(); UpdateDisplay();
_window.Display();
UpdatePreviousKeyStates(); UpdatePreviousKeyStates();
} }
@@ -69,111 +89,238 @@ internal class Program
Console.WriteLine($"\nSimulation stopped after {_updateCount} updates"); Console.WriteLine($"\nSimulation stopped after {_updateCount} updates");
} }
private void InitializeDisplay()
{
// Initialize display texts
for (int i = 0; i < 30; i++)
{
Text text = new Text("", _font, 16);
text.FillColor = _textColor;
text.Position = new Vector2f(20, 20 + i * 24);
_displayTexts.Add(text);
}
// Tachometer
_tachometerBackground = new RectangleShape(new Vector2f(200, 200));
_tachometerBackground.Position = new Vector2f(700, 50);
_tachometerBackground.FillColor = new Color(40, 40, 50);
_tachometerBackground.OutlineThickness = 2;
_tachometerBackground.OutlineColor = Color.White;
_tachometerNeedle = new RectangleShape(new Vector2f(80, 4));
_tachometerNeedle.Position = new Vector2f(800, 150);
_tachometerNeedle.FillColor = Color.Red;
_tachometerNeedle.Origin = new Vector2f(70, 2);
// Speedometer
_speedometerBackground = new RectangleShape(new Vector2f(200, 200));
_speedometerBackground.Position = new Vector2f(700, 300);
_speedometerBackground.FillColor = new Color(40, 40, 50);
_speedometerBackground.OutlineThickness = 2;
_speedometerBackground.OutlineColor = Color.White;
_speedometerNeedle = new RectangleShape(new Vector2f(80, 4));
_speedometerNeedle.Position = new Vector2f(800, 400);
_speedometerNeedle.FillColor = Color.Green;
_speedometerNeedle.Origin = new Vector2f(70, 2);
}
private void UpdateDisplay()
{
_window.Clear(_backgroundColor);
UpdateDisplayTexts();
foreach (var text in _displayTexts)
{
if (!string.IsNullOrEmpty(text.DisplayedString))
_window.Draw(text);
}
DrawGauges();
DrawKeyBindings();
}
private void UpdateDisplayTexts()
{
// Clear all text
for (int i = 0; i < _displayTexts.Count; i++)
{
_displayTexts[i].DisplayedString = "";
_displayTexts[i].FillColor = _textColor;
}
// Update text - using safe indexing
int line = 0;
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "ENGINE";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" RPM: {car.Engine.RPM,7:F0}";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Torque: {car.Engine.GetTorqueOutput(),7:F0} Nm";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Throttle: {car.Engine.GetActualThrottle() * 100,6:F1}%";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Power: {car.Engine.CurrentPower / 1000,6:F1} kW";
if (line < _displayTexts.Count)
{
_displayTexts[line].DisplayedString = $" Status: {(car.Engine.IsRunning ? "RUNNING" : "STALLED")}";
_displayTexts[line].FillColor = car.Engine.IsRunning ? _textColor : _warningColor;
line++;
}
line++; // Blank line
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "DRIVETRAIN";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Gear: {car.Drivetrain.GetCurrentGearName(),3} (Ratio: {car.Drivetrain.GearRatio:F2}:1)";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Clutch: {car.ClutchInput * 100,6:F1}% disengaged";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Clutch Torque: {car.Drivetrain.ClutchTorque,6:F0} Nm";
if (line < _displayTexts.Count)
{
_displayTexts[line].DisplayedString = $" Clutch Slip: {car.Drivetrain.GetClutchSlipPercent(),6:F1}%";
_displayTexts[line].FillColor = car.Drivetrain.GetClutchSlipPercent() > 50 ? _warningColor : _textColor;
line++;
}
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Transmitted: {car.Drivetrain.TransmittedPower / 1000,6:F1} kW";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Speed Diff: {car.Drivetrain.GetSpeedDifferenceRPM(),6:F0} RPM";
line++; // Blank line
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "VEHICLE";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Speed: {car.Speed * 3.6f,7:F1} km/h";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Wheel RPM: {car.WheelSystem.RPM,7:F0}";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Brake: {car.BrakeInput * 100,6:F1}%";
line++; // Blank line
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "FORCES";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Total Resistance: {car.CalculateTotalResistanceForce(),6:F1} N";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Drag: {car.CalculateDragForce(),6:F1} N";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Rolling: {car.CalculateRollingResistanceForce(),6:F1} N";
line++; // Blank line
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "ENERGY";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Engine: {car.Engine.FlywheelEnergy,7:F0} J";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Total: {car.WheelSystem.TotalEnergy,7:F0} J";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Wheel Rotation: {car.WheelSystem.GetRotationalEnergy(),7:F0} J";
if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Car Translation: {car.WheelSystem.GetTranslationalEnergy(),7:F0} J";
}
private void DrawGauges()
{
_window.Draw(_tachometerBackground);
float rpmRatio = Math.Clamp(car.Engine.RPM / 8000f, 0f, 1f);
float tachometerAngle = -90 + (270 * rpmRatio);
_tachometerNeedle.Rotation = tachometerAngle;
_window.Draw(_tachometerNeedle);
Text tachLabel = new Text("RPM", _font, 20);
tachLabel.FillColor = Color.White;
tachLabel.Position = new Vector2f(770, 70);
_window.Draw(tachLabel);
Text rpmText = new Text($"{car.Engine.RPM:F0}", _font, 24);
rpmText.FillColor = car.Engine.RPM > 7000 ? _warningColor : Color.White;
rpmText.Position = new Vector2f(765, 100);
_window.Draw(rpmText);
_window.Draw(_speedometerBackground);
float speedRatio = Math.Clamp(car.Speed * 3.6f / 200f, 0f, 1f);
float speedometerAngle = -90 + (270 * speedRatio);
_speedometerNeedle.Rotation = speedometerAngle;
_window.Draw(_speedometerNeedle);
Text speedLabel = new Text("SPEED", _font, 20);
speedLabel.FillColor = Color.White;
speedLabel.Position = new Vector2f(770, 320);
_window.Draw(speedLabel);
Text speedText = new Text($"{car.Speed * 3.6f:F1} km/h", _font, 24);
speedText.FillColor = Color.White;
speedText.Position = new Vector2f(750, 350);
_window.Draw(speedText);
Text gearText = new Text($"GEAR {car.Drivetrain.GetCurrentGearName()}", _font, 28);
gearText.FillColor = _highlightColor;
gearText.Position = new Vector2f(750, 520);
_window.Draw(gearText);
}
private void DrawKeyBindings()
{
Text controls = new Text("CONTROLS\n\nW/S: Throttle/Brake\nUp/Down: Clutch\nLeft/Right: Gear Up/Down\nSpace: Toggle Force Clutch\nESC: Quit", _font, 14);
controls.FillColor = new Color(180, 180, 180);
controls.Position = new Vector2f(250, 625);
_window.Draw(controls);
}
private void InitializeTrackedKeys() private void InitializeTrackedKeys()
{ {
// Initialize all keys we care about var keys = new[] {
var keysToTrack = new Keyboard.Key[] Keyboard.Key.W, Keyboard.Key.S,
{ Keyboard.Key.Down, Keyboard.Key.Up, Keyboard.Key.Space,
Keyboard.Key.W, Keyboard.Key.Escape,
Keyboard.Key.Up, Keyboard.Key.Right, Keyboard.Key.Left
Keyboard.Key.Down,
Keyboard.Key.B,
Keyboard.Key.Space,
Keyboard.Key.Left,
Keyboard.Key.Right,
Keyboard.Key.Escape
}; };
foreach (var key in keysToTrack) foreach (var key in keys)
{ {
_currentKeyStates[key] = false;
_previousKeyStates[key] = false; _previousKeyStates[key] = false;
_currentKeyStates[key] = false;
} }
} }
private void OnKeyPressed(object sender, KeyEventArgs e) private void OnKeyPressed(object sender, KeyEventArgs e)
{ {
var key = e.Code; if (_currentKeyStates.ContainsKey(e.Code))
_currentKeyStates[e.Code] = true;
// Update current state
if (_currentKeyStates.ContainsKey(key))
{
_currentKeyStates[key] = true;
}
} }
private void OnKeyReleased(object sender, KeyEventArgs e) private void OnKeyReleased(object sender, KeyEventArgs e)
{ {
var key = e.Code; if (_currentKeyStates.ContainsKey(e.Code))
_currentKeyStates[e.Code] = false;
// Update current state
if (_currentKeyStates.ContainsKey(key))
{
_currentKeyStates[key] = false;
}
} }
private void ProcessInput(float deltaTime) private void ProcessInput(float deltaTime)
{ {
// quit // Throttle/Brake
if (IsKeyDown(Keyboard.Key.Escape)) if (_currentKeyStates[Keyboard.Key.W])
{ car.ThrottleInput += deltaTime * 4f;
_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 else
{ car.ThrottleInput -= deltaTime * 8f;
car.ThrottleInput = Math.Max(car.ThrottleInput - 10f * deltaTime, 0f);
}
// brake if (_currentKeyStates[Keyboard.Key.S])
if (IsKeyDown(Keyboard.Key.B)) car.BrakeInput += deltaTime * 4f;
{
car.BrakeInput = Math.Min(car.BrakeInput + 1f * deltaTime, 1.0f);
}
else else
{ car.BrakeInput -= deltaTime * 8f;
car.BrakeInput = Math.Max(car.BrakeInput - 4f * deltaTime, 0f);
}
// clutch car.ThrottleInput = Math.Clamp(car.ThrottleInput, 0f, 1f);
if (IsKeyDown(Keyboard.Key.Up)) car.BrakeInput = Math.Clamp(car.BrakeInput, 0f, 1f);
{
car.ClutchInput = Math.Min(car.ClutchInput + 0.1f * deltaTime, 1.0f);
}
else if (IsKeyDown(Keyboard.Key.Down))
{
car.ClutchInput = Math.Max(car.ClutchInput - 0.1f * deltaTime, 0f);
}
// clutch // Clutch
if (IsKeyDown(Keyboard.Key.Up)) if (_currentKeyStates[Keyboard.Key.Up])
{ car.ClutchInput += deltaTime * 0.5f;
car.ClutchInput = Math.Min(car.ClutchInput + 1f * deltaTime, 1.0f); if(_currentKeyStates[Keyboard.Key.Down])
} car.ClutchInput -= deltaTime * 0.5f;
else if (IsKeyDown(Keyboard.Key.Down))
{
car.ClutchInput = Math.Max(car.ClutchInput - 1f * deltaTime, 0f);
}
// gear car.ClutchInput = Math.Clamp(car.ClutchInput, 0f, 1f);
if (WasKeyPressed(Keyboard.Key.Left))
{ // Toggle force clutch
car.Drivetrain.GearDown(); if (_currentKeyStates[Keyboard.Key.Space] && !_previousKeyStates[Keyboard.Key.Space])
} car.ForceClutch = !car.ForceClutch;
else if (WasKeyPressed(Keyboard.Key.Right))
{ // Gear changes
if (_currentKeyStates[Keyboard.Key.Right] && !_previousKeyStates[Keyboard.Key.Right])
car.Drivetrain.GearUp(); car.Drivetrain.GearUp();
} if (_currentKeyStates[Keyboard.Key.Left] && !_previousKeyStates[Keyboard.Key.Left])
car.Drivetrain.GearDown();
// Quit
if (_currentKeyStates[Keyboard.Key.Escape])
_isRunning = false;
} }
private void UpdatePreviousKeyStates() private void UpdatePreviousKeyStates()
@@ -181,31 +328,8 @@ internal class Program
var keys = new List<Keyboard.Key>(_currentKeyStates.Keys); var keys = new List<Keyboard.Key>(_currentKeyStates.Keys);
foreach (var key in keys) foreach (var key in keys)
{ {
if (_previousKeyStates.ContainsKey(key))
_previousKeyStates[key] = _currentKeyStates[key]; _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();
}
} }

BIN
Car simulation/arial.ttf Normal file

Binary file not shown.