mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-09-10 02:03:42 +08:00
c7767bddfe
* Refactor and extend ModelRenderer for custom Sunnypilot logic Refactored `ModelRenderer` to `ModelRendererSP` with enhanced features such as lane line updates, path drawing, and lead management for Sunnypilot. Introduced new methods for model updates, lead drawing, and improved path rendering with experimental mode support. Ensured compatibility by integrating with Sunnypilot-specific HUD and camera components. * Update selfdrive/ui/sunnypilot/qt/onroad/model.cc * Refactor `ModelRenderer` for modularity Moved constants and `get_path_length_idx` function to the header file for reuse and clarity. Updated `drawPath` and related methods to better handle surface dimensions, improving rendering flexibility. Made key functions virtual to allow further customization in derived classes. * Cleaning logic on ModelRenderSP Given that we've refactored slightly the original ModelRender, we no longer need to duplicate the logic on our own implementation --------- Co-authored-by: DevTekVE <devtekve@gmail.com> Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
238 lines
9.6 KiB
C++
238 lines
9.6 KiB
C++
#include "selfdrive/ui/qt/onroad/model.h"
|
|
|
|
void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
|
auto *s = uiState();
|
|
auto &sm = *(s->sm);
|
|
// Check if data is up-to-date
|
|
if (sm.rcv_frame("liveCalibration") < s->scene.started_frame ||
|
|
sm.rcv_frame("modelV2") < s->scene.started_frame) {
|
|
return;
|
|
}
|
|
|
|
clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN);
|
|
experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
|
|
longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
|
path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0];
|
|
|
|
painter.save();
|
|
|
|
const auto &model = sm["modelV2"].getModelV2();
|
|
const auto &radar_state = sm["radarState"].getRadarState();
|
|
const auto &lead_one = radar_state.getLeadOne();
|
|
|
|
update_model(model, lead_one);
|
|
drawLaneLines(painter);
|
|
drawPath(painter, model, surface_rect);
|
|
|
|
if (longitudinal_control && sm.alive("radarState")) {
|
|
update_leads(radar_state, model.getPosition());
|
|
const auto &lead_two = radar_state.getLeadTwo();
|
|
if (lead_one.getStatus()) {
|
|
drawLead(painter, lead_one, lead_vertices[0], surface_rect);
|
|
}
|
|
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
|
|
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
|
|
}
|
|
}
|
|
|
|
painter.restore();
|
|
}
|
|
|
|
void ModelRenderer::update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) {
|
|
for (int i = 0; i < 2; ++i) {
|
|
const auto &lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo();
|
|
if (lead_data.getStatus()) {
|
|
float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())];
|
|
mapToScreen(lead_data.getDRel(), -lead_data.getYRel(), z + path_offset_z, &lead_vertices[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelRenderer::update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) {
|
|
const auto &model_position = model.getPosition();
|
|
float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE);
|
|
|
|
// update lane lines
|
|
const auto &lane_lines = model.getLaneLines();
|
|
const auto &line_probs = model.getLaneLineProbs();
|
|
int max_idx = get_path_length_idx(lane_lines[0], max_distance);
|
|
for (int i = 0; i < std::size(lane_line_vertices); i++) {
|
|
lane_line_probs[i] = line_probs[i];
|
|
mapLineToPolygon(lane_lines[i], 0.025 * lane_line_probs[i], 0, &lane_line_vertices[i], max_idx);
|
|
}
|
|
|
|
// update road edges
|
|
const auto &road_edges = model.getRoadEdges();
|
|
const auto &edge_stds = model.getRoadEdgeStds();
|
|
for (int i = 0; i < std::size(road_edge_vertices); i++) {
|
|
road_edge_stds[i] = edge_stds[i];
|
|
mapLineToPolygon(road_edges[i], 0.025, 0, &road_edge_vertices[i], max_idx);
|
|
}
|
|
|
|
// update path
|
|
if (lead.getStatus()) {
|
|
const float lead_d = lead.getDRel() * 2.;
|
|
max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance);
|
|
}
|
|
max_idx = get_path_length_idx(model_position, max_distance);
|
|
mapLineToPolygon(model_position, 0.9, path_offset_z, &track_vertices, max_idx, false);
|
|
}
|
|
|
|
void ModelRenderer::drawLaneLines(QPainter &painter) {
|
|
// lanelines
|
|
for (int i = 0; i < std::size(lane_line_vertices); ++i) {
|
|
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(lane_line_probs[i], 0.0, 0.7)));
|
|
painter.drawPolygon(lane_line_vertices[i]);
|
|
}
|
|
|
|
// road edges
|
|
for (int i = 0; i < std::size(road_edge_vertices); ++i) {
|
|
painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - road_edge_stds[i], 0.0, 1.0)));
|
|
painter.drawPolygon(road_edge_vertices[i]);
|
|
}
|
|
}
|
|
|
|
void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height) {
|
|
QLinearGradient bg(0, height, 0, 0);
|
|
if (experimental_mode) {
|
|
// The first half of track_vertices are the points for the right side of the path
|
|
const auto &acceleration = model.getAcceleration().getX();
|
|
const int max_len = std::min<int>(track_vertices.length() / 2, acceleration.size());
|
|
|
|
for (int i = 0; i < max_len; ++i) {
|
|
// Some points are out of frame
|
|
int track_idx = max_len - i - 1; // flip idx to start from bottom right
|
|
if (track_vertices[track_idx].y() < 0 || track_vertices[track_idx].y() > height) continue;
|
|
|
|
// Flip so 0 is bottom of frame
|
|
float lin_grad_point = (height - track_vertices[track_idx].y()) / height;
|
|
|
|
// speed up: 120, slow down: 0
|
|
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
|
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
|
path_hue = int(path_hue * 100 + 0.5) / 100;
|
|
|
|
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
|
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
|
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
|
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
|
|
|
// Skip a point, unless next is last
|
|
i += (i + 2) < max_len ? 1 : 0;
|
|
}
|
|
|
|
} else {
|
|
updatePathGradient(bg);
|
|
}
|
|
|
|
painter.setBrush(bg);
|
|
painter.drawPolygon(track_vertices);
|
|
}
|
|
|
|
void ModelRenderer::updatePathGradient(QLinearGradient &bg) {
|
|
static const QColor throttle_colors[] = {
|
|
QColor::fromHslF(148. / 360., 0.94, 0.51, 0.4),
|
|
QColor::fromHslF(112. / 360., 1.0, 0.68, 0.35),
|
|
QColor::fromHslF(112. / 360., 1.0, 0.68, 0.0)};
|
|
|
|
static const QColor no_throttle_colors[] = {
|
|
QColor::fromHslF(148. / 360., 0.0, 0.95, 0.4),
|
|
QColor::fromHslF(112. / 360., 0.0, 0.95, 0.35),
|
|
QColor::fromHslF(112. / 360., 0.0, 0.95, 0.0),
|
|
};
|
|
|
|
// Transition speed; 0.1 corresponds to 0.5 seconds at UI_FREQ
|
|
constexpr float transition_speed = 0.1f;
|
|
|
|
// Start transition if throttle state changes
|
|
bool allow_throttle = (*uiState()->sm)["longitudinalPlan"].getLongitudinalPlan().getAllowThrottle() || !longitudinal_control;
|
|
if (allow_throttle != prev_allow_throttle) {
|
|
prev_allow_throttle = allow_throttle;
|
|
// Invert blend factor for a smooth transition when the state changes mid-animation
|
|
blend_factor = std::max(1.0f - blend_factor, 0.0f);
|
|
}
|
|
|
|
const QColor *begin_colors = allow_throttle ? no_throttle_colors : throttle_colors;
|
|
const QColor *end_colors = allow_throttle ? throttle_colors : no_throttle_colors;
|
|
if (blend_factor < 1.0f) {
|
|
blend_factor = std::min(blend_factor + transition_speed, 1.0f);
|
|
}
|
|
|
|
// Set gradient colors by blending the start and end colors
|
|
bg.setColorAt(0.0f, blendColors(begin_colors[0], end_colors[0], blend_factor));
|
|
bg.setColorAt(0.5f, blendColors(begin_colors[1], end_colors[1], blend_factor));
|
|
bg.setColorAt(1.0f, blendColors(begin_colors[2], end_colors[2], blend_factor));
|
|
}
|
|
|
|
QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float t) {
|
|
if (t == 1.0f) return end;
|
|
return QColor::fromRgbF(
|
|
(1 - t) * start.redF() + t * end.redF(),
|
|
(1 - t) * start.greenF() + t * end.greenF(),
|
|
(1 - t) * start.blueF() + t * end.blueF(),
|
|
(1 - t) * start.alphaF() + t * end.alphaF());
|
|
}
|
|
|
|
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
|
|
const QPointF &vd, const QRect &surface_rect) {
|
|
const float speedBuff = 10.;
|
|
const float leadBuff = 40.;
|
|
const float d_rel = lead_data.getDRel();
|
|
const float v_rel = lead_data.getVRel();
|
|
|
|
float fillAlpha = 0;
|
|
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;
|
|
float x = std::clamp<float>(vd.x(), 0.f, surface_rect.width() - sz / 2);
|
|
float y = std::min<float>(vd.y(), surface_rect.height() - sz * 0.6);
|
|
|
|
float g_xo = sz / 5;
|
|
float g_yo = sz / 10;
|
|
|
|
QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}};
|
|
painter.setBrush(QColor(218, 202, 37, 255));
|
|
painter.drawPolygon(glow, std::size(glow));
|
|
|
|
// chevron
|
|
QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}};
|
|
painter.setBrush(QColor(201, 34, 49, fillAlpha));
|
|
painter.drawPolygon(chevron, std::size(chevron));
|
|
}
|
|
|
|
// Projects a point in car to space to the corresponding point in full frame image space.
|
|
bool ModelRenderer::mapToScreen(float in_x, float in_y, float in_z, QPointF *out) {
|
|
Eigen::Vector3f input(in_x, in_y, in_z);
|
|
auto pt = car_space_transform * input;
|
|
*out = QPointF(pt.x() / pt.z(), pt.y() / pt.z());
|
|
return clip_region.contains(*out);
|
|
}
|
|
|
|
void ModelRenderer::mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
|
|
QPolygonF *pvd, int max_idx, bool allow_invert) {
|
|
const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ();
|
|
QPointF left, right;
|
|
pvd->clear();
|
|
for (int i = 0; i <= max_idx; i++) {
|
|
// highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera
|
|
if (line_x[i] < 0) continue;
|
|
|
|
bool l = mapToScreen(line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left);
|
|
bool r = mapToScreen(line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right);
|
|
if (l && r) {
|
|
// For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts
|
|
if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) {
|
|
continue;
|
|
}
|
|
pvd->push_back(left);
|
|
pvd->push_front(right);
|
|
}
|
|
}
|
|
}
|