From 2fb5fbf3addebb48f86762b79f1ea6d9b26f4421 Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Sun, 5 Jul 2026 04:39:52 -0400 Subject: [PATCH] Raylib Rainbow Road Update (All) --- selfdrive/ui/mici/onroad/model_renderer.py | 27 ++------- selfdrive/ui/onroad/model_renderer.py | 27 ++------- selfdrive/ui/onroad/starpilot/rainbow_path.py | 56 +++++++++++++++++++ 3 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 selfdrive/ui/onroad/starpilot/rainbow_path.py diff --git a/selfdrive/ui/mici/onroad/model_renderer.py b/selfdrive/ui/mici/onroad/model_renderer.py index 56252ab71..7d8d7fc8c 100644 --- a/selfdrive/ui/mici/onroad/model_renderer.py +++ b/selfdrive/ui/mici/onroad/model_renderer.py @@ -8,6 +8,7 @@ from openpilot.common.constants import CV from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, is_stock_color_scheme, with_alpha +from openpilot.selfdrive.ui.onroad.starpilot.rainbow_path import RainbowPath from openpilot.selfdrive.ui.lib.starpilot_visuals import lead_indicator_enabled from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.mici.onroad import blend_colors @@ -19,8 +20,6 @@ from openpilot.system.ui.widgets import Widget CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 -RAINBOW_GRADIENT_COLOR_COUNT = 19 -RAINBOW_SCROLL_SPEED_DEG_PER_SEC = 60.0 STOCK_LANE_LINES_COLOR = rl.Color(255, 255, 255, 255) DEFAULT_LANE_LINES_WIDTH = 4.0 DEFAULT_PATH_WIDTH = 6.1 @@ -91,6 +90,7 @@ class ModelRenderer(Widget): colors=[], stops=[], ) + self._rainbow_path = RainbowPath() # Get longitudinal control setting from car parameters self._params = Params() @@ -147,6 +147,8 @@ class ModelRenderer(Widget): self._transform_dirty = False self._draw_lane_lines() + if self._params.get_bool("RainbowPath", default=False) and sm.valid.get('carState', False): + self._rainbow_path.update(max(sm['carState'].vEgo, 0.0)) self._draw_path(sm) if render_lead_indicator and radar_state: @@ -261,8 +263,7 @@ class ModelRenderer(Widget): """Pre-calculate experimental mode gradient colors""" use_rainbow = self._params.get_bool("RainbowPath", default=False) if use_rainbow: - gradient_bottom, gradient_top = self._get_visible_gradient_bounds() - self._exp_gradient = self._build_rainbow_gradient(gradient_bottom, gradient_top) + self._exp_gradient = self._rainbow_path.get_gradient(0.0, 1.0) return if not self._experimental_mode or not self._params.get_bool("AccelerationPath", default=True): @@ -319,24 +320,6 @@ class ModelRenderer(Widget): gradient_top = np.clip((float(np.min(visible_track_y)) - self._rect.y) / self._rect.height, 0.0, 1.0) return float(gradient_bottom), float(gradient_top) - def _build_rainbow_gradient(self, gradient_bottom: float, gradient_top: float) -> Gradient: - hue_offset = (rl.get_time() * RAINBOW_SCROLL_SPEED_DEG_PER_SEC) % 360.0 - stops = [i / (RAINBOW_GRADIENT_COLOR_COUNT - 1) for i in range(RAINBOW_GRADIENT_COLOR_COUNT)] - colors = [] - - for i, stop in enumerate(stops): - hue_progress = i / RAINBOW_GRADIENT_COLOR_COUNT - path_hue = (hue_progress * 360.0 - hue_offset) % 360.0 - alpha = np.interp(stop, [0.0, 1.0], [0.48, 0.18]) - colors.append(self._hsla_to_color(path_hue / 360.0, 1.0, 0.5, alpha)) - - return Gradient( - start=(0.0, gradient_bottom), - end=(0.0, gradient_top), - colors=colors, - stops=stops, - ) - def _update_lead_vehicle(self, d_rel, v_rel, point, rect): speed_buff, lead_buff = 10.0, 40.0 diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 8a000fe4a..6e6d43b65 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -8,6 +8,7 @@ from openpilot.common.params import Params from openpilot.common.constants import CV from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, is_stock_color_scheme, with_alpha +from openpilot.selfdrive.ui.onroad.starpilot.rainbow_path import RainbowPath from openpilot.selfdrive.ui.lib.starpilot_visuals import lead_indicator_enabled from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.system.ui.lib.application import gui_app @@ -18,8 +19,6 @@ from openpilot.system.ui.widgets import Widget CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 -RAINBOW_GRADIENT_COLOR_COUNT = 19 -RAINBOW_SCROLL_SPEED_DEG_PER_SEC = 60.0 STOCK_LANE_LINES_COLOR = rl.Color(255, 255, 255, 255) DEFAULT_LANE_LINES_WIDTH = 4.0 DEFAULT_PATH_EDGE_WIDTH = 20.0 @@ -86,6 +85,7 @@ class ModelRenderer(Widget): colors=[], stops=[], ) + self._rainbow_path = RainbowPath() # Get longitudinal control setting from car parameters self._params = Params() @@ -127,6 +127,8 @@ class ModelRenderer(Widget): self._use_rainbow = self._params.get_bool('RainbowPath', default=False) self._use_accel_path = self._params.get_bool('AccelerationPath', default=True) self._is_metric = self._params.get_bool('IsMetric') + if self._use_rainbow and sm.valid.get('carState', False): + self._rainbow_path.update(max(sm['carState'].vEgo, 0.0)) lead_info_enabled = self._lead_info_enabled render_lead_indicator = ( (self._longitudinal_control or lead_info_enabled) @@ -270,8 +272,7 @@ class ModelRenderer(Widget): def _update_experimental_gradient(self): """Pre-calculate experimental mode gradient colors""" if self._use_rainbow: - gradient_bottom, gradient_top = self._get_visible_gradient_bounds() - self._exp_gradient = self._build_rainbow_gradient(gradient_bottom, gradient_top) + self._exp_gradient = self._rainbow_path.get_gradient(0.0, 1.0) return if not self._experimental_mode or not self._use_accel_path: @@ -330,24 +331,6 @@ class ModelRenderer(Widget): gradient_top = np.clip((float(np.min(visible_track_y)) - self._rect.y) / self._rect.height, 0.0, 1.0) return float(gradient_bottom), float(gradient_top) - def _build_rainbow_gradient(self, gradient_bottom: float, gradient_top: float) -> Gradient: - hue_offset = (rl.get_time() * RAINBOW_SCROLL_SPEED_DEG_PER_SEC) % 360.0 - stops = [i / (RAINBOW_GRADIENT_COLOR_COUNT - 1) for i in range(RAINBOW_GRADIENT_COLOR_COUNT)] - colors = [] - - for i, stop in enumerate(stops): - hue_progress = i / RAINBOW_GRADIENT_COLOR_COUNT - path_hue = (hue_progress * 360.0 - hue_offset) % 360.0 - alpha = np.interp(stop, [0.0, 1.0], [0.48, 0.18]) - colors.append(self._hsla_to_color(path_hue / 360.0, 1.0, 0.5, alpha)) - - return Gradient( - start=(0.0, gradient_bottom), - end=(0.0, gradient_top), - colors=colors, - stops=stops, - ) - def _update_lead_vehicle(self, d_rel, v_rel, point, rect): speed_buff, lead_buff = 10.0, 40.0 diff --git a/selfdrive/ui/onroad/starpilot/rainbow_path.py b/selfdrive/ui/onroad/starpilot/rainbow_path.py new file mode 100644 index 000000000..693fe21be --- /dev/null +++ b/selfdrive/ui/onroad/starpilot/rainbow_path.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import colorsys + +import pyray as rl +from openpilot.system.ui.lib.shader_polygon import Gradient + + +HUE_RANGE = 120.0 +SCROLL_DEG_PER_SEC_PER_MPS = 5.0 +ALPHA_BOTTOM = 0.5 +ALPHA_TOP = 0.1 +NUM_STOPS = 12 + + +def _hsla_to_color(h: float, s: float, l: float, a: float) -> rl.Color: + rgb = colorsys.hls_to_rgb(h, l, s) + return rl.Color(int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255), int(a * 255)) + + +class RainbowPath: + """Rainbow path renderer ported from the FrogPilot/StarPilot Qt C++ UI implementation.""" + + def __init__(self) -> None: + self._hue_offset: float = 0.0 + self._last_time: float = 0.0 + + def update(self, speed_ms: float) -> None: + """Accumulate hue offset from vehicle speed. Call once per frame.""" + now = rl.get_time() + dt = now - self._last_time if self._last_time > 0.0 else 0.0 + self._last_time = now + if speed_ms > 0.0 and dt > 0.0: + self._hue_offset = (self._hue_offset + speed_ms * SCROLL_DEG_PER_SEC_PER_MPS * dt) % 360.0 + + def get_gradient(self, gradient_bottom: float, gradient_top: float) -> Gradient: + """Build a Gradient compatible with draw_polygon(). + + Args: + gradient_bottom: Normalized y-position (0-1) of the path bottom. + gradient_top: Normalized y-position (0-1) of the path top. + """ + stops = [i / (NUM_STOPS - 1) for i in range(NUM_STOPS)] + colors = [] + + for stop in stops: + path_hue = (stop * HUE_RANGE + self._hue_offset) % 360.0 + alpha = ALPHA_BOTTOM + (ALPHA_TOP - ALPHA_BOTTOM) * stop + colors.append(_hsla_to_color(path_hue / 360.0, 1.0, 0.5, alpha)) + + return Gradient( + start=(0.0, gradient_bottom), + end=(0.0, gradient_top), + colors=colors, + stops=stops, + )