diff --git a/system/ui/onroad/augmented_road_view.py b/system/ui/onroad/augmented_road_view.py index 316167a828..b4b6eef12c 100644 --- a/system/ui/onroad/augmented_road_view.py +++ b/system/ui/onroad/augmented_road_view.py @@ -1,5 +1,6 @@ import numpy as np import pyray as rl +from enum import Enum from cereal import messaging, log from msgq.visionipc import VisionStreamType @@ -10,8 +11,15 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler +OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] +UI_BORDER_SIZE = 30 + +class BorderStatus(Enum): + DISENGAGED = rl.Color(0x17, 0x33, 0x49, 0xc8) # Blue for disengaged state + OVERRIDE = rl.Color(0x91, 0x9b, 0x95, 0xf1) # Gray for override state + ENGAGED = rl.Color(0x17, 0x86, 0x44, 0xf1) # Green for engaged state class AugmentedRoadView(CameraView): @@ -29,6 +37,7 @@ class AugmentedRoadView(CameraView): self._last_calib_time: float = 0 self._last_rect_dims = (0.0, 0.0) self._cached_matrix: np.ndarray | None = None + self._content_rect = rl.Rectangle() self.model_renderer = ModelRenderer() @@ -36,6 +45,26 @@ class AugmentedRoadView(CameraView): # Update calibration before rendering self._update_calibration() + # Create inner content area with border padding + self._content_rect = rl.Rectangle( + rect.x + UI_BORDER_SIZE, + rect.y + UI_BORDER_SIZE, + rect.width - 2 * UI_BORDER_SIZE, + rect.height - 2 * UI_BORDER_SIZE, + ) + + # Draw colored border based on driving state + self._draw_border(rect) + + # Enable scissor mode to clip all rendering within content rectangle boundaries + # This creates a rendering viewport that prevents graphics from drawing outside the border + rl.begin_scissor_mode( + int(self._content_rect.x), + int(self._content_rect.y), + int(self._content_rect.width), + int(self._content_rect.height) + ) + # Render the base camera view super().render(rect) @@ -44,7 +73,21 @@ class AugmentedRoadView(CameraView): # - Path prediction # - Lead vehicle indicators # - Additional features - self.model_renderer.draw(rect, self.sm) + self.model_renderer.draw(self._content_rect, self.sm) + + # End clipping region + rl.end_scissor_mode() + + def _draw_border(self, rect: rl.Rectangle): + state = self.sm["selfdriveState"] + if state.state in (OpState.preEnabled, OpState.overriding): + status = BorderStatus.OVERRIDE + elif state.enabled: + status = BorderStatus.ENGAGED + else: + status = BorderStatus.DISENGAGED + + rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, status.value) def _update_calibration(self): # Update device camera if not already set @@ -71,7 +114,7 @@ class AugmentedRoadView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: # Check if we can use cached matrix calib_time = self.sm.recv_frame.get('liveCalibration', 0) - current_dims = (rect.width, rect.height) + current_dims = (self._content_rect.width, self._content_rect.height) if (self._last_calib_time == calib_time and self._last_rect_dims == current_dims and self._cached_matrix is not None): @@ -89,7 +132,8 @@ class AugmentedRoadView(CameraView): kep = calib_transform @ inf_point # Calculate center points and dimensions - w, h = current_dims + x, y = self._content_rect.x, self._content_rect.y + w, h = self._content_rect.width, self._content_rect.height cx, cy = intrinsic[0, 2], intrinsic[1, 2] # Calculate max allowed offsets with margins @@ -117,8 +161,8 @@ class AugmentedRoadView(CameraView): ]) video_transform = np.array([ - [zoom, 0.0, (w / 2 - x_offset) - (cx * zoom)], - [0.0, zoom, (h / 2 - y_offset) - (cy * zoom)], + [zoom, 0.0, (w / 2 + x - x_offset) - (cx * zoom)], + [0.0, zoom, (h / 2 + y - y_offset) - (cy * zoom)], [0.0, 0.0, 1.0] ]) self.model_renderer.set_transform(video_transform @ calib_transform)