add basic engine simulation + working gauge
This commit is contained in:
parent
17fbfb3426
commit
2ac3bf166b
BIN
__pycache__/engine.cpython-313.pyc
Normal file
BIN
__pycache__/engine.cpython-313.pyc
Normal file
Binary file not shown.
145
engine.py
145
engine.py
@ -1,50 +1,127 @@
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
class Engine:
|
class Engine:
|
||||||
rpm = 0
|
def __init__(self,
|
||||||
throttle = 0
|
idle_rpm=750,
|
||||||
load = 0
|
max_rpm=7000,
|
||||||
instant_torque = 0
|
redline_rpm=6500,
|
||||||
gear = 0
|
flywheel_inertia=0.025,
|
||||||
flywheel_inertia = 0
|
fuel_efficiency=0.3,
|
||||||
idle_rpm = 750
|
torque_curve=None):
|
||||||
max_rpm = 6500
|
|
||||||
|
|
||||||
fuel_rate = 0
|
# General stuff
|
||||||
afr = 0
|
self.rpm = idle_rpm
|
||||||
coolant_temp = 0
|
self.throttle = 0.0
|
||||||
oil_temp = 0
|
self.load = 0.0
|
||||||
|
self.gear = None
|
||||||
|
self.ignition = True
|
||||||
|
self.revCut = False
|
||||||
|
|
||||||
ignition_timing = 0
|
self.idle_rpm = idle_rpm
|
||||||
|
self.max_rpm = max_rpm
|
||||||
|
self.redline_rpm = redline_rpm
|
||||||
|
self.flywheel_inertia = flywheel_inertia
|
||||||
|
self.fuel_efficiency = fuel_efficiency
|
||||||
|
self.torque_curve = torque_curve or self.default_torque_curve
|
||||||
|
self.instant_torque = 0
|
||||||
|
self.wheel_speed = 0
|
||||||
|
self.engineFriction = 5
|
||||||
|
self.peak_torque = 163 # Nm -> 120 ft/lb
|
||||||
|
self.engine_brake_strength = 40 # Nm
|
||||||
|
self.engine_brake_torque = 0
|
||||||
|
self.starting = False
|
||||||
|
|
||||||
ambient_temp = 0
|
# Fluids
|
||||||
altitude = 0
|
self.oil_temp = 0
|
||||||
intake_pressure = 0
|
self.oil_pressure = 0
|
||||||
vehicle_mass = 0
|
self.oil_capacity = 0
|
||||||
drivetrain_loss = 0
|
|
||||||
|
|
||||||
oil_pressure = 0
|
self.coolant_temp = 0
|
||||||
turbo_boost = 0
|
self.coolant_pressure = 0
|
||||||
kr = 0
|
self.coolant_capacity = 0
|
||||||
exhaust_temp = 0
|
|
||||||
|
self.fuel_capacity = 0
|
||||||
|
|
||||||
|
# Fuel
|
||||||
|
|
||||||
|
self.fuel_rate = 0
|
||||||
|
self.afr = 0
|
||||||
|
|
||||||
|
# Timing stuff
|
||||||
|
self.kr = 0
|
||||||
|
self.timing_advance = 0
|
||||||
|
self.ignition_timing = 0
|
||||||
|
|
||||||
|
# Exhaust stuff
|
||||||
|
self.exhaust_temp = 0
|
||||||
|
self.turbo_boost = 0
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
self.ambient_temp = 0
|
||||||
|
self.altitude = 0
|
||||||
|
self.intake_pressure = 0
|
||||||
|
self.vehicle_mass = 0
|
||||||
|
self.drivetrain_loss = 0
|
||||||
|
|
||||||
|
# Just a parabolic curve for now
|
||||||
|
def default_torque_curve(self, rpm):
|
||||||
|
rpm_ratio = rpm / self.max_rpm
|
||||||
|
return max(0.0, -4 * (rpm_ratio - 0.5) ** 2 + 1)
|
||||||
|
|
||||||
|
def start(self, dt):
|
||||||
|
self.ignition = True
|
||||||
|
self.starting = True
|
||||||
|
|
||||||
def start(self):
|
|
||||||
rpm = 250
|
|
||||||
|
|
||||||
def update(self, throttle, load, dt):
|
def update(self, throttle, load, dt):
|
||||||
|
|
||||||
if not self.running:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.throttle = throttle
|
self.throttle = throttle
|
||||||
self.load = load
|
self.load = load
|
||||||
|
|
||||||
# Apply simplified engine RPM dynamics
|
if self.starting:
|
||||||
rpm_change = (throttle - load) * self.torque_curve(self.rpm) / self.flywheel_inertia
|
available_torque = self.torque_curve(self.rpm)
|
||||||
self.rpm += rpm_change * dt
|
self.instant_torque = (available_torque * self.peak_torque) + 10
|
||||||
self.rpm = max(self.idle_rpm, min(self.rpm, self.max_rpm))
|
rpm_change = (self.instant_torque / self.flywheel_inertia)
|
||||||
|
self.rpm += rpm_change * dt
|
||||||
|
if self.rpm > 2000:
|
||||||
|
self.starting = False
|
||||||
|
throttle = 0
|
||||||
|
|
||||||
|
# Idle -- Eventually should have learning alg to find the % throttle needed
|
||||||
|
if (self.rpm < self.idle_rpm):
|
||||||
|
self.throttle = 0.1
|
||||||
|
|
||||||
|
# Rev limit
|
||||||
|
if (self.rpm > self.redline_rpm):
|
||||||
|
self.revCut = True
|
||||||
|
|
||||||
|
if self.revCut and self.rpm > self.redline_rpm - 100:
|
||||||
|
self.throttle = 0
|
||||||
|
else:
|
||||||
|
self.revCut = False
|
||||||
|
|
||||||
|
|
||||||
|
# Engine friction
|
||||||
|
if (self.rpm > 0):
|
||||||
|
friction_force = self.engineFriction
|
||||||
|
else:
|
||||||
|
friction_force = 0
|
||||||
|
|
||||||
|
# Engine braking torque (If throttle closed)
|
||||||
|
if self.throttle < 0.1 and self.rpm > self.idle_rpm + 200 or not self.ignition:
|
||||||
|
engine_brake_torque = self.engine_brake_strength * (self.rpm / self.max_rpm)
|
||||||
|
else:
|
||||||
|
engine_brake_torque = 0
|
||||||
|
|
||||||
|
# Calculate torque
|
||||||
|
if self.ignition:
|
||||||
|
available_torque = self.torque_curve(self.rpm)
|
||||||
|
else:
|
||||||
|
available_torque = 0
|
||||||
|
|
||||||
|
self.instant_torque = (self.throttle * available_torque * self.peak_torque) - engine_brake_torque - friction_force
|
||||||
|
rpm_change = (self.instant_torque / self.flywheel_inertia)
|
||||||
|
self.rpm += rpm_change * dt
|
||||||
|
|
||||||
self.torque = self.torque_curve(self.rpm) * throttle
|
|
||||||
self.fuel_rate = self.rpm * throttle * self.fuel_efficiency
|
self.fuel_rate = self.rpm * throttle * self.fuel_efficiency
|
||||||
|
|
||||||
self._update_temps(dt)
|
#self._update_temps(dt)
|
||||||
|
63
main.py
63
main.py
@ -12,6 +12,12 @@ dt = 0
|
|||||||
|
|
||||||
player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)
|
player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)
|
||||||
|
|
||||||
|
# Text Setup
|
||||||
|
pygame.font.init()
|
||||||
|
font = pygame.font.SysFont(None, 64)
|
||||||
|
|
||||||
|
|
||||||
|
# Gauge Setup
|
||||||
rpm_pos = pygame.Vector2(screen.get_width() * 0.25, screen.get_height() / 2)
|
rpm_pos = pygame.Vector2(screen.get_width() * 0.25, screen.get_height() / 2)
|
||||||
spd_pos = pygame.Vector2(screen.get_width() * 0.75, screen.get_height() / 2)
|
spd_pos = pygame.Vector2(screen.get_width() * 0.75, screen.get_height() / 2)
|
||||||
|
|
||||||
@ -20,7 +26,7 @@ gauge_radius = screen.get_height() / 5
|
|||||||
# Angle for gauge
|
# Angle for gauge
|
||||||
angle_rad = math.radians(145)
|
angle_rad = math.radians(145)
|
||||||
|
|
||||||
needle_color = "black"
|
needle_color = "white"
|
||||||
needle_width = 4
|
needle_width = 4
|
||||||
tip_radius = needle_width
|
tip_radius = needle_width
|
||||||
|
|
||||||
@ -29,29 +35,50 @@ offset_vector = pygame.Vector2(
|
|||||||
math.sin(angle_rad) * gauge_radius
|
math.sin(angle_rad) * gauge_radius
|
||||||
)
|
)
|
||||||
|
|
||||||
e = engine()
|
e = Engine()
|
||||||
|
throttle = 0
|
||||||
|
|
||||||
|
def map_value_to_angle(value, min_val, max_val):
|
||||||
|
clamped = max(min_val, min(value, max_val)) # Can change this later
|
||||||
|
return math.radians(150 + 270 * (clamped - min_val) / (max_val - min_val))
|
||||||
|
|
||||||
while running:
|
while running:
|
||||||
|
mX, mY = pygame.mouse.get_pos()
|
||||||
# poll for events
|
# poll for events
|
||||||
# pygame.QUIT event means the user clicked X to close your window
|
# pygame.QUIT event means the user clicked X to close your window
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if (pygame.key.name(event.key) == 'v'):
|
||||||
|
if e.ignition:
|
||||||
|
e.ignition = False
|
||||||
|
else:
|
||||||
|
e.start(dt)
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
running = False
|
running = False
|
||||||
|
|
||||||
# fill the screen with a color to wipe away anything from last frame
|
# fill the screen with a color to wipe away anything from last frame
|
||||||
screen.fill("white")
|
screen.fill("white")
|
||||||
|
|
||||||
# RPM
|
rpm_text = font.render(f"RPM: {int(e.rpm)}", True, (0, 0, 0))
|
||||||
# Min = 0 at 45 degrees
|
rpm_rect = rpm_text.get_rect(center=(screen.get_width() // 2, screen.get_height() // 4))
|
||||||
# Max = 8 at 315 degrees
|
screen.blit(rpm_text, rpm_rect)
|
||||||
pygame.draw.circle(screen, "red", rpm_pos, gauge_radius)
|
|
||||||
rpm_tip = rpm_pos + offset_vector
|
|
||||||
pygame.draw.line(screen, needle_color, rpm_pos, rpm_tip, needle_width)
|
|
||||||
pygame.draw.circle(screen, needle_color, rpm_tip, tip_radius)
|
|
||||||
|
|
||||||
# Trans Speed
|
rpm_text = font.render(f"Torque: {int(e.instant_torque)}", True, (0, 0, 0))
|
||||||
# Min = 0 at 45 degrees
|
rpm_rect = rpm_text.get_rect(center=(screen.get_width() // 2, screen.get_height() // 4 +100))
|
||||||
# Max = 160 at 315 degrees
|
screen.blit(rpm_text, rpm_rect)
|
||||||
|
|
||||||
|
rpm_angle = map_value_to_angle(e.rpm, 0, 8000)
|
||||||
|
|
||||||
|
# RPM Gauge
|
||||||
|
speed_angle = map_value_to_angle(e.wheel_speed, 0, 160)
|
||||||
|
rpm_vector = pygame.Vector2(math.cos(rpm_angle), math.sin(rpm_angle)) * gauge_radius
|
||||||
|
pygame.draw.circle(screen, "black", rpm_pos, gauge_radius)
|
||||||
|
pygame.draw.line(screen, needle_color, rpm_pos, rpm_pos + rpm_vector, needle_width)
|
||||||
|
|
||||||
|
|
||||||
|
# Trans Gauge
|
||||||
|
|
||||||
|
speed_vector = pygame.Vector2(math.cos(speed_angle), math.sin(speed_angle)) * gauge_radius
|
||||||
pygame.draw.circle(screen, "red", spd_pos, gauge_radius)
|
pygame.draw.circle(screen, "red", spd_pos, gauge_radius)
|
||||||
spd_tip = spd_pos + offset_vector
|
spd_tip = spd_pos + offset_vector
|
||||||
pygame.draw.line(screen, needle_color, spd_pos, spd_tip, needle_width)
|
pygame.draw.line(screen, needle_color, spd_pos, spd_tip, needle_width)
|
||||||
@ -61,13 +88,20 @@ while running:
|
|||||||
|
|
||||||
keys = pygame.key.get_pressed()
|
keys = pygame.key.get_pressed()
|
||||||
if keys[pygame.K_w]:
|
if keys[pygame.K_w]:
|
||||||
player_pos.y -= 300 * dt
|
e.rpm += 600 * dt
|
||||||
if keys[pygame.K_s]:
|
if keys[pygame.K_s]:
|
||||||
player_pos.y += 300 * dt
|
e.rpm -= 600 * dt
|
||||||
if keys[pygame.K_a]:
|
if keys[pygame.K_a]:
|
||||||
player_pos.x -= 300 * dt
|
player_pos.x -= 300 * dt
|
||||||
if keys[pygame.K_d]:
|
if keys[pygame.K_d]:
|
||||||
player_pos.x += 300 * dt
|
player_pos.x += 300 * dt
|
||||||
|
if keys[pygame.K_r]:
|
||||||
|
e.rpm = e.idle_rpm
|
||||||
|
|
||||||
|
# Throttle (Using mouse pos)
|
||||||
|
throttle = 1 - (mY / screen.get_height())
|
||||||
|
#print(throttle)
|
||||||
|
|
||||||
|
|
||||||
# flip() the display to put your work on screen
|
# flip() the display to put your work on screen
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
@ -76,5 +110,6 @@ while running:
|
|||||||
# dt is delta time in seconds since last frame, used for framerate-
|
# dt is delta time in seconds since last frame, used for framerate-
|
||||||
# independent physics.
|
# independent physics.
|
||||||
dt = clock.tick(60) / 1000
|
dt = clock.tick(60) / 1000
|
||||||
|
e.update(throttle, 0, dt)
|
||||||
|
|
||||||
pygame.quit()
|
pygame.quit()
|
Loading…
x
Reference in New Issue
Block a user