#include "selfdrive/ui/paint.h" #include #ifdef __APPLE__ #include #define NANOVG_GL3_IMPLEMENTATION #define nvgCreate nvgCreateGL3 #else #include #define NANOVG_GLES3_IMPLEMENTATION #define nvgCreate nvgCreateGLES3 #endif #define NANOVG_GLES3_IMPLEMENTATION #include #include #include "selfdrive/hardware/hw.h" static void draw_chevron(UIState *s, float x, float y, float sz, NVGcolor fillColor, NVGcolor glowColor) { // glow float g_xo = sz/5; float g_yo = sz/10; nvgBeginPath(s->vg); nvgMoveTo(s->vg, x+(sz*1.35)+g_xo, y+sz+g_yo); nvgLineTo(s->vg, x, y-g_xo); nvgLineTo(s->vg, x-(sz*1.35)-g_xo, y+sz+g_yo); nvgClosePath(s->vg); nvgFillColor(s->vg, glowColor); nvgFill(s->vg); // chevron nvgBeginPath(s->vg); nvgMoveTo(s->vg, x+(sz*1.25), y+sz); nvgLineTo(s->vg, x, y); nvgLineTo(s->vg, x-(sz*1.25), y+sz); nvgClosePath(s->vg); nvgFillColor(s->vg, fillColor); nvgFill(s->vg); } static void draw_lead(UIState *s, const cereal::RadarState::LeadData::Reader &lead_data, const vertex_data &vd) { // Draw lead car indicator auto [x, y] = vd; float fillAlpha = 0; float speedBuff = 10.; float leadBuff = 40.; float d_rel = lead_data.getDRel(); float v_rel = lead_data.getVRel(); if (d_rel < leadBuff) { fillAlpha = 255*(1.0-(d_rel/leadBuff)); if (v_rel < 0) { fillAlpha += 255*(-1*(v_rel/speedBuff)); } fillAlpha = (int)(fmin(fillAlpha, 255)); } float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; x = std::clamp(x, 0.f, s->fb_w - sz / 2); y = std::fmin(s->fb_h - sz * .6, y); draw_chevron(s, x, y, sz, nvgRGBA(201, 34, 49, fillAlpha), COLOR_YELLOW); } static void ui_draw_line(UIState *s, const line_vertices_data &vd, NVGcolor *color, NVGpaint *paint) { if (vd.cnt == 0) return; const vertex_data *v = &vd.v[0]; nvgBeginPath(s->vg); nvgMoveTo(s->vg, v[0].x, v[0].y); for (int i = 1; i < vd.cnt; i++) { nvgLineTo(s->vg, v[i].x, v[i].y); } nvgClosePath(s->vg); if (color) { nvgFillColor(s->vg, *color); } else if (paint) { nvgFillPaint(s->vg, *paint); } nvgFill(s->vg); } static void ui_draw_vision_lane_lines(UIState *s) { const UIScene &scene = s->scene; NVGpaint track_bg; if (!scene.end_to_end) { // paint lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { NVGcolor color = nvgRGBAf(1.0, 1.0, 1.0, scene.lane_line_probs[i]); ui_draw_line(s, scene.lane_line_vertices[i], &color, nullptr); } // paint road edges for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { NVGcolor color = nvgRGBAf(1.0, 0.0, 0.0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0)); ui_draw_line(s, scene.road_edge_vertices[i], &color, nullptr); } track_bg = nvgLinearGradient(s->vg, s->fb_w, s->fb_h, s->fb_w, s->fb_h * .4, COLOR_WHITE, COLOR_WHITE_ALPHA(0)); } else { track_bg = nvgLinearGradient(s->vg, s->fb_w, s->fb_h, s->fb_w, s->fb_h * .4, COLOR_RED, COLOR_RED_ALPHA(0)); } // paint path ui_draw_line(s, scene.track_vertices, nullptr, &track_bg); } // Draw all world space objects. static void ui_draw_world(UIState *s) { nvgScissor(s->vg, 0, 0, s->fb_w, s->fb_h); // Draw lane edges and vision/mpc tracks ui_draw_vision_lane_lines(s); // Draw lead indicators if openpilot is handling longitudinal if (s->scene.longitudinal_control) { auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); auto lead_two = (*s->sm)["radarState"].getRadarState().getLeadTwo(); if (lead_one.getStatus()) { draw_lead(s, lead_one, s->scene.lead_vertices[0]); } if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { draw_lead(s, lead_two, s->scene.lead_vertices[1]); } } nvgResetScissor(s->vg); } static void ui_draw_vision_header(UIState *s) { NVGpaint gradient = nvgLinearGradient(s->vg, 0, header_h - (header_h / 2.5), 0, header_h, nvgRGBAf(0, 0, 0, 0.45), nvgRGBAf(0, 0, 0, 0)); nvgBeginPath(s->vg); nvgRect(s->vg, 0, 0, s->fb_w, header_h); nvgFillPaint(s->vg, gradient); nvgFill(s->vg); } static void ui_draw_vision(UIState *s) { const UIScene *scene = &s->scene; // Draw augmented elements if (scene->world_objects_visible) { ui_draw_world(s); } // TODO: move this to Qt ui_draw_vision_header(s); } void ui_draw(UIState *s, int w, int h) { // Update intrinsics matrix after possible wide camera toggle change if (s->fb_w != w || s->fb_h != h) { ui_resize(s, w, h); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); nvgBeginFrame(s->vg, s->fb_w, s->fb_h, 1.0f); ui_draw_vision(s); nvgEndFrame(s->vg); glDisable(GL_BLEND); } void ui_nvg_init(UIState *s) { // on EON, we enable MSAA s->vg = Hardware::EON() ? nvgCreate(0) : nvgCreate(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG); assert(s->vg); } void ui_resize(UIState *s, int width, int height) { s->fb_w = width; s->fb_h = height; auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; float zoom = ZOOM / intrinsic_matrix.v[0]; if (s->wide_camera) { zoom *= 0.5; } // Apply transformation such that video pixel coordinates match video // 1) Put (0, 0) in the middle of the video // 2) Apply same scaling as video // 3) Put (0, 0) in top left corner of video s->car_space_transform.reset(); s->car_space_transform.translate(width / 2, height / 2 + y_offset) .scale(zoom, zoom) .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); }