BigUI WIP: Aethergauge Curvey

This commit is contained in:
firestarsdog
2026-05-23 04:55:30 -04:00
parent b88c886592
commit 2ef8f8ebef
2 changed files with 138 additions and 57 deletions
+137 -56
View File
@@ -13,6 +13,8 @@ class AetherGaugeData:
label: str = ""
animation: str = "none" # e.g. "down_arrows"
color: rl.Color = rl.WHITE
indicator_type: str = "none" # e.g. "road_curve"
indicator_value: float = 0.0 # value used by indicator (e.g. curvature)
class AetherGaugeSource:
def is_active(self) -> bool:
@@ -50,7 +52,9 @@ class CurveSpeedSource(AetherGaugeSource):
unit=speed_unit,
label="CURVE",
animation="down_arrows",
color=color
color=color,
indicator_type="road_curve",
indicator_value=state['curvature']
)
class AetherGauge:
@@ -63,73 +67,150 @@ class AetherGauge:
return source.get_gauge_data()
return None
def render(self, rect: rl.Rectangle, font_bold: rl.Font, font_medium: rl.Font):
def render(self, rect: rl.Rectangle, font_bold: rl.Font, font_medium: rl.Font, current_speed: float):
data = self.get_active_data()
if not data:
return
cx = rect.x + rect.width / 2
cy = rect.y + 340
# Vertical center of travel speed text is exactly at rect.y + 180 (matching hud_renderer.py)
cy_speed = rect.y + 180
if data.animation == "down_arrows":
self._render_down_arrows(cx, cy, data.color)
text_y = cy + 50
else:
text_y = cy
speed_text = str(round(current_speed))
speed_text_size = measure_text_cached(font_bold, speed_text, 176)
self._render_text(cx, text_y, data, font_bold, font_medium)
# Position the curving road widget to the left of the speed text, center-aligned vertically
icon_cx = cx - speed_text_size.x / 2 - 70.0
def _render_down_arrows(self, cx: float, cy: float, color: rl.Color):
progress = (rl.get_time() * 1.5) % 1.0
spacing = 16.0
chevron_w = 20.0
chevron_h = 10.0
if data.indicator_type == "road_curve":
self._render_unified_road(icon_cx, cy_speed, data, font_bold, font_medium)
def _render_unified_road(self, icx: float, icy: float, data: AetherGaugeData, font_bold: rl.Font, font_medium: rl.Font):
# Dimensions of the road projection
half_size = 40.0
bottom = icy + half_size
top = icy - half_size
# Non-linear curvature offset
abs_curv = abs(data.indicator_value)
sign = math.copysign(1.0, data.indicator_value) if data.indicator_value != 0.0 else 1.0
curve_offset = max(-half_size, min(half_size, sign * (abs_curv ** 0.5) * 300.0))
# 3D perspective widths
w_bottom = 28.0
w_top = 14.0
thickness = 4.0
# Generate points along the road path
num_segments = 16
points_left = []
points_right = []
for i in range(num_segments + 1):
t = i / num_segments
# Center path quadratic bend
offset = curve_offset * (t ** 2)
cx_t = icx + offset
y_t = bottom - t * 80.0
# Perspective width interpolation
w_t = w_bottom - t * (w_bottom - w_top)
points_left.append(rl.Vector2(int(cx_t - w_t), int(y_t)))
points_right.append(rl.Vector2(int(cx_t + w_t), int(y_t)))
# A. Draw glowing road surface fill (glowing HUD look)
fill_color = rl.Color(data.color.r, data.color.g, data.color.b, 35)
for i in range(num_segments):
rl.draw_triangle(points_left[i], points_right[i], points_left[i+1], fill_color)
rl.draw_triangle(points_right[i], points_right[i+1], points_left[i+1], fill_color)
# B. Draw left and right road boundaries
shadow_color = rl.Color(0, 0, 0, 100)
for i in range(num_segments):
# Shadow lines
rl.draw_line_ex(rl.Vector2(points_left[i].x, points_left[i].y + 2), rl.Vector2(points_left[i+1].x, points_left[i+1].y + 2), thickness, shadow_color)
rl.draw_line_ex(rl.Vector2(points_right[i].x, points_right[i].y + 2), rl.Vector2(points_right[i+1].x, points_right[i+1].y + 2), thickness, shadow_color)
# Main boundaries
rl.draw_line_ex(points_left[i], points_left[i+1], thickness, data.color)
rl.draw_line_ex(points_right[i], points_right[i+1], thickness, data.color)
# C. Draw animating chevrons flowing down the road center path
progress = (rl.get_time() * 1.2) % 1.0
for i in range(3):
y_off = (i + progress) * spacing
y = cy + y_off
# Chevron fraction t (flowing down from 1.0 to 0.0)
t = 1.0 - ((i + progress) / 3.0)
# Calculate local tangent vector for rotation
t_next = min(1.0, t + 0.05)
cx_t = icx + curve_offset * (t ** 2)
cy_t = bottom - t * 80.0
cx_next = icx + curve_offset * (t_next ** 2)
cy_next = bottom - t_next * 80.0
dx = cx_next - cx_t
dy = cy_next - cy_t
len_v = math.sqrt(dx**2 + dy**2)
if len_v > 0.001:
dir_up_x = dx / len_v
dir_up_y = dy / len_v
else:
dir_up_x = 0.0
dir_up_y = -1.0
dir_right_x = -dir_up_y
dir_right_y = dir_up_x
# Perspective scaling of chevrons
chevron_w = 14.0 - t * 6.0
chevron_h = chevron_w * 0.6
chevron_thick = max(2.0, 4.0 - t * 2.0)
# Vertices
lx = cx_t - dir_right_x * chevron_w + dir_up_x * chevron_h
ly = cy_t - dir_right_y * chevron_w + dir_up_y * chevron_h
rx = cx_t + dir_right_x * chevron_w + dir_up_x * chevron_h
ry = cy_t + dir_right_y * chevron_w + dir_up_y * chevron_h
# Alpha envelope fading in at horizon, bright in middle, fading out at bottom
alpha_factor = math.sin(t * math.pi)
alpha = int(data.color.a * alpha_factor)
chev_color = rl.Color(data.color.r, data.color.g, data.color.b, alpha)
chev_shadow = rl.Color(0, 0, 0, int(alpha * 0.5))
# Draw chevron with shadow
rl.draw_line_ex(rl.Vector2(int(lx), int(ly + 1.5)), rl.Vector2(int(cx_t), int(cy_t + 1.5)), chevron_thick, chev_shadow)
rl.draw_line_ex(rl.Vector2(int(rx), int(ry + 1.5)), rl.Vector2(int(cx_t), int(cy_t + 1.5)), chevron_thick, chev_shadow)
rl.draw_line_ex(rl.Vector2(int(lx), int(ly)), rl.Vector2(int(cx_t), int(cy_t)), chevron_thick, chev_color)
rl.draw_line_ex(rl.Vector2(int(rx), int(ry)), rl.Vector2(int(cx_t), int(cy_t)), chevron_thick, chev_color)
dist_normalized = (i + progress) / 3.0
alpha_factor = math.sin(dist_normalized * math.pi)
alpha = int(color.a * alpha_factor)
chevron_color = rl.Color(color.r, color.g, color.b, alpha)
# Drop shadow (black with half of chevron's alpha)
shadow_color = rl.Color(0, 0, 0, int(alpha * 0.5))
rl.draw_line_ex(rl.Vector2(int(cx - chevron_w), int(y - chevron_h + 2)), rl.Vector2(int(cx), int(y + 2)), thickness, shadow_color)
rl.draw_line_ex(rl.Vector2(int(cx + chevron_w), int(y - chevron_h + 2)), rl.Vector2(int(cx), int(y + 2)), thickness, shadow_color)
# Main chevron lines
rl.draw_line_ex(rl.Vector2(int(cx - chevron_w), int(y - chevron_h)), rl.Vector2(int(cx), int(y)), thickness, chevron_color)
rl.draw_line_ex(rl.Vector2(int(cx + chevron_w), int(y - chevron_h)), rl.Vector2(int(cx), int(y)), thickness, chevron_color)
def _render_text(self, cx: float, cy: float, data: AetherGaugeData, font_bold: rl.Font, font_medium: rl.Font):
speed_text = data.text
unit_text = data.unit
speed_size = measure_text_cached(font_bold, speed_text, 48)
unit_size = measure_text_cached(font_medium, unit_text, 24)
# D. Draw clean floating target speed directly below the road base
self._draw_mini_cradle(icx, bottom + 25, data.text, data.unit, data.color, font_bold, font_medium)
def _draw_mini_cradle(self, cx: float, cy: float, target_speed: str, unit: str, color: rl.Color, font_bold: rl.Font, font_medium: rl.Font):
val_size = measure_text_cached(font_bold, target_speed, 48)
unit_size = measure_text_cached(font_medium, unit, 24)
gap = 6
total_w = speed_size.x + gap + unit_size.x
total_w = val_size.x + gap + unit_size.x
start_x = cx - total_w / 2
# Draw outline for speed
speed_pos = rl.Vector2(int(start_x), int(cy))
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x - 1, speed_pos.y - 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x + 1, speed_pos.y - 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x - 1, speed_pos.y + 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x + 1, speed_pos.y + 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, speed_text, speed_pos, 48, 0, rl.WHITE)
# Draw outline for unit
unit_y = cy + (speed_size.y - unit_size.y) - 2
unit_pos = rl.Vector2(int(start_x + speed_size.x + gap), int(unit_y))
rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x - 1, unit_pos.y - 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x + 1, unit_pos.y - 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x - 1, unit_pos.y + 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x + 1, unit_pos.y + 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit_text, unit_pos, 24, 0, data.color)
# Speed numbers
val_pos = rl.Vector2(int(start_x), int(cy - val_size.y / 2))
rl.draw_text_ex(font_bold, target_speed, rl.Vector2(val_pos.x - 1, val_pos.y - 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, target_speed, rl.Vector2(val_pos.x + 1, val_pos.y - 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, target_speed, rl.Vector2(val_pos.x - 1, val_pos.y + 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, target_speed, rl.Vector2(val_pos.x + 1, val_pos.y + 1), 48, 0, rl.BLACK)
rl.draw_text_ex(font_bold, target_speed, val_pos, 48, 0, color)
# Unit text
unit_y = cy + (val_size.y - unit_size.y) / 2 - 2
unit_pos = rl.Vector2(int(start_x + val_size.x + gap), int(unit_y))
rl.draw_text_ex(font_medium, unit, rl.Vector2(unit_pos.x - 1, unit_pos.y - 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit, rl.Vector2(unit_pos.x + 1, unit_pos.y - 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit, rl.Vector2(unit_pos.x - 1, unit_pos.y + 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit, rl.Vector2(unit_pos.x + 1, unit_pos.y + 1), 24, 0, rl.BLACK)
rl.draw_text_ex(font_medium, unit, unit_pos, 24, 0, rl.Color(255, 255, 255, 200))
@@ -62,7 +62,7 @@ class StarPilotOnroadView(AugmentedRoadView):
self._render_road_name()
self._render_standstill_timer()
self._render_developer_metrics()
self._aethergauge.render(self._content_rect, self._font_bold, self._font_medium)
self._aethergauge.render(self._content_rect, self._font_bold, self._font_medium, current_speed=self._hud_renderer.speed)
self._render_bottom_row_widgets()
self._render_pedals()