#include "viewer_debug.hpp" #include "core.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { std::string degC(reinterpret_cast(u8"°C")); std::string deg(reinterpret_cast(u8"°")); std::atomic g_run{ false }; std::thread g_thr; std::atomic g_draw_boxes{ false }; std::atomic g_color_by_distance{ true }; std::atomic g_mode_map{ false }; std::atomic g_use_slam{ false }; std::atomic g_sensor_invertido{ false }; std::atomic g_show_axis{ false }; // colormap tipo "jet" (0..1 -> RGB) static inline void ColorMapJet(float v, uint8_t& r, uint8_t& g, uint8_t& b) { if (v < 0) v = 0; if (v > 1) v = 1; float x = 4.0f * v; float rf = std::clamp(std::min(x - 1.5f, -x + 4.5f), 0.0f, 1.0f); float gf = std::clamp(std::min(x - 0.5f, -x + 3.5f), 0.0f, 1.0f); float bf = std::clamp(std::min(x + 0.5f, -x + 2.5f), 0.0f, 1.0f); r = (uint8_t)(rf * 255.f); g = (uint8_t)(gf * 255.f); b = (uint8_t)(bf * 255.f); } // recolore de acordo com o modo: distância radial (true) ou altura Z (false) static inline void colorizePoint(pcl::PointXYZRGB& p, bool by_dist) { uint8_t r, g, b; if (by_dist) { float dist = std::sqrt(p.x * p.x + p.y * p.y + p.z * p.z); ColorMapJet(dist / 5.0f, r, g, b); // 5 m = referência; ajuste se quiser } else { // normaliza Z entre [-0.2, 1.5] como no código antigo float zmin = -0.2f, zmax = 1.5f; float v = (p.z - zmin) / std::max(1e-3f, (zmax - zmin)); ColorMapJet(v, r, g, b); } p.r = r; p.g = g; p.b = b; } // ---- helpers HUD ---- static void addHudTop(pcl::visualization::PCLVisualizer::Ptr& viewer) { viewer->removeShape("hud_top"); auto rw = viewer->getRenderWindow(); int* size = rw->GetSize(); int win_w = size[0], win_h = size[1]; const int margin = 10; const int font_px = 14; const int line_h = font_px + 4; const int x = margin; const int y = win_h - margin - line_h; auto temp = Core::get_temperature_c(); auto rpy = Core::get_attitude(); std::stringstream ss; ss.setf(std::ios::fixed); ss << std::setprecision(1); ss << "TEMP: " << temp << degC << " |" << " R:" << Core::rad2deg(rpy.roll) << deg << " P:" << Core::rad2deg(rpy.pitch) << deg << " Y:" << Core::rad2deg(rpy.yaw) << deg; viewer->addText(ss.str(), x, y, font_px, 1.0, 1.0, 1.0, "hud_top"); } static void addHudBottom(pcl::visualization::PCLVisualizer::Ptr& viewer) { viewer->removeShape("hud_bottom"); std::stringstream ss; ss << (g_mode_map.load() ? "MODE: MAP " : "MODE: LIVE ") << (g_color_by_distance.load() ? "DISTANCIA " : "ALTURA ") << "| SLAM: " << (g_use_slam.load() ? "ON" : "OFF") << " | BOX: " << (g_draw_boxes.load() ? "ON" : "OFF"); viewer->addText(ss.str(), 10, 10, 14, 1.0, 1.0, 1.0, "hud_bottom"); } // (simplificado) converte CloudRGB ? pcl static pcl::PointCloud::Ptr toPcl(const CloudRGB& c) { auto pc = pcl::PointCloud::Ptr(new pcl::PointCloud()); pc->points.reserve(c.pts.size()); for (auto& q : c.pts) { pcl::PointXYZRGB p; p.x = q.x; p.y = q.y; p.z = q.z; p.r = q.r; p.g = q.g; p.b = q.b; pc->points.push_back(p); } pc->width = (uint32_t)pc->points.size(); pc->height = 1; pc->is_dense = true; return pc; } // (debug) desenha “bússola” de distâncias static void drawDistances3D(pcl::visualization::PCLVisualizer::Ptr& viewer) { using std::string; for (auto id : { "linha_frente","linha_tras","linha_esquerda","linha_direita","linha_cima","linha_baixo", "texto_frente","texto_tras","texto_esquerda","texto_direita","texto_cima","texto_baixo" }) viewer->removeShape(id); auto d = Core::get_distances(); auto draw = [&](const string& idLine, const string& idText, float vx, float vy, float vz, float dist, float cr, float cg, float cb, const char* label) { if (dist <= 0.001f) dist = 0.0f; Eigen::Vector3f dst = Eigen::Vector3f(vx, vy, vz).normalized() * dist; viewer->addLine(pcl::PointXYZ(0, 0, 0), pcl::PointXYZ(dst.x(), dst.y(), dst.z()), cr, cg, cb, idLine); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 3, idLine); char txt[64]; if (dist > 0.0f) std::snprintf(txt, sizeof(txt), "%s: %.0f cm", label, dist * 100.f); else std::snprintf(txt, sizeof(txt), "%s: --", label); viewer->addText3D(txt, pcl::PointXYZ(dst.x(), dst.y(), dst.z()), 0.05, cr, cg, cb, idText); }; draw("linha_frente", "texto_frente", 1, 0, 0, d.front, 1, 0, 0, "frente"); draw("linha_tras", "texto_tras", -1, 0, 0, d.back, 0.8, 0, 0, "tras"); draw("linha_direita", "texto_direita", 0, 1, 0, d.right, 0, 1, 0, "direita"); draw("linha_esquerda", "texto_esquerda", 0, -1, 0, d.left, 0, 0.8, 0, "esquerda"); draw("linha_cima", "texto_cima", 0, 0, 1, d.up, 0, 0, 1, "cima"); draw("linha_baixo", "texto_baixo", 0, 0, -1, d.down, 0, 0, 0.8, "baixo"); } // compara AABB com tolerância static inline bool approx_equal(const BBox3D& a, const BBox3D& b, float eps = 1e-4f) { auto ae = [&](float x, float y) { return std::fabs(x - y) <= eps; }; return ae(a.min_x, b.min_x) && ae(a.min_y, b.min_y) && ae(a.min_z, b.min_z) && ae(a.max_x, b.max_x) && ae(a.max_y, b.max_y) && ae(a.max_z, b.max_z); } static const int BBOX_EDGES[12][2] = { {0,1},{1,2},{2,3},{3,0}, {4,5},{5,6},{6,7},{7,4}, {0,4},{1,5},{2,6},{3,7} }; static uint64_t g_last_draw_ms = 0; static std::string g_actor_id = "bboxes_all"; static size_t g_last_n = 0; // constrói um único polydata de linhas com TODAS as bboxes static vtkSmartPointer build_bbox_poly(const std::vector& boxes, size_t max_boxes = SIZE_MAX) { auto pts = vtkSmartPointer::New(); auto lines = vtkSmartPointer::New(); const size_t N = std::min(boxes.size(), max_boxes); pts->SetDataTypeToFloat(); pts->Allocate(8 * N); lines->AllocateEstimate(12 * N, 2); vtkIdType base = 0; for (size_t i = 0; i < N; ++i) { const auto& b = boxes[i]; const float x0 = b.min_x, y0 = b.min_y, z0 = b.min_z; const float x1 = b.max_x, y1 = b.max_y, z1 = b.max_z; // 8 vértices vtkIdType id0 = pts->InsertNextPoint(x0, y0, z0); vtkIdType id1 = pts->InsertNextPoint(x1, y0, z0); vtkIdType id2 = pts->InsertNextPoint(x1, y1, z0); vtkIdType id3 = pts->InsertNextPoint(x0, y1, z0); vtkIdType id4 = pts->InsertNextPoint(x0, y0, z1); vtkIdType id5 = pts->InsertNextPoint(x1, y0, z1); vtkIdType id6 = pts->InsertNextPoint(x1, y1, z1); vtkIdType id7 = pts->InsertNextPoint(x0, y1, z1); const vtkIdType ids[8] = { id0,id1,id2,id3,id4,id5,id6,id7 }; // 12 arestas for (int e = 0; e < 12; ++e) { auto ln = vtkSmartPointer::New(); ln->GetPointIds()->SetId(0, ids[BBOX_EDGES[e][0]]); ln->GetPointIds()->SetId(1, ids[BBOX_EDGES[e][1]]); lines->InsertNextCell(ln); } base += 8; } auto poly = vtkSmartPointer::New(); poly->SetPoints(pts); poly->SetLines(lines); return poly; } // desenha/atualiza em 1 único ator static void draw_bboxes_one_actor(pcl::visualization::PCLVisualizer::Ptr& viewer, uint64_t now_ms, uint64_t min_interval_ms = 120, // ~8 Hz size_t max_boxes = 200) // cota de segurança { if (now_ms - g_last_draw_ms < min_interval_ms) return; auto boxes = Core::get_bboxes(); if (boxes.empty()) { if (g_last_n) { viewer->removeShape(g_actor_id); g_last_n = 0; } g_last_draw_ms = now_ms; return; } // (Opcional) cull simples: mantenha só as K mais próximas/do maior tamanho etc. // std::partial_sort(...); boxes.resize(std::min(boxes.size(), max_boxes)); auto poly = build_bbox_poly(boxes, max_boxes); // remove e recria SÓ UM ator viewer->removeShape(g_actor_id); viewer->addModelFromPolyData(poly, g_actor_id); // propriedades visuais leves viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 1.0, 0.0, g_actor_id); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 1.5, g_actor_id); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_OPACITY, 1.0, g_actor_id); // desliga “efeitos” viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_SHADING, 0.0, g_actor_id); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION, pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, g_actor_id); g_last_n = std::min(boxes.size(), max_boxes); g_last_draw_ms = now_ms; } void KeyboardCallback(const pcl::visualization::KeyboardEvent& event, void*) { if (!event.keyDown()) return; const std::string k = event.getKeySym(); auto toggle = [](std::atomic& f) { // troca atômica: inverte o bit de forma thread-safe bool expected = f.load(std::memory_order_relaxed); while (!f.compare_exchange_weak(expected, !expected, std::memory_order_relaxed)) {} }; if (k == "m" || k == "M") { toggle(g_mode_map); std::cout << "\n[KEY] Modo: " << (g_mode_map.load() ? "MAP" : "LIVE") << std::endl; } else if (k == "s" || k == "S") { toggle(g_use_slam); std::cout << "\n[KEY] SLAM: " << (g_use_slam.load() ? "ATIVADO (ICP ON)" : "DESATIVADO") << std::endl; } else if (k == "d" || k == "D") { toggle(g_color_by_distance); std::cout << "\n[KEY] Color: " << (g_color_by_distance.load() ? "DISTANCIA" : "ALTURA") << std::endl; } else if (k == "b" || k == "B") { toggle(g_draw_boxes); std::cout << "\n[KEY] Caixas: " << (g_draw_boxes.load() ? "ON" : "OFF") << std::endl; } else if (k == "i" || k == "I") { toggle(g_sensor_invertido); std::cout << "\n[KEY] Sensor invertido: " << (g_sensor_invertido.load() ? "ON" : "OFF") << std::endl; } else if (k == "a" || k == "A") { toggle(g_show_axis); std::cout << "\n[KEY] Mostrar eixos: " << (g_show_axis.load() ? "ON" : "OFF") << std::endl; } else if (k == "c" || k == "C") { Core::ResetGlobalMap(); std::cout << "\n[KEY] MAP: limpo\n"; } } void loop() { auto viewer = pcl::visualization::PCLVisualizer::Ptr(new pcl::visualization::PCLVisualizer("Mid-360 Debug Viewer")); viewer->setBackgroundColor(0, 0, 0); viewer->addCoordinateSystem(1.0); viewer->initCameraParameters(); auto interactor = viewer->getRenderWindow()->GetInteractor(); // === Keyboard === viewer->registerKeyboardCallback(&KeyboardCallback, nullptr); // nuvem “fonte” (live/map) — aqui vamos desenhar a “live” auto live = Core::get_cloud_live(); auto cloud_pcl = toPcl(live); pcl::visualization::PointCloudColorHandlerRGBField rgb(cloud_pcl); viewer->addPointCloud(cloud_pcl, rgb, "cloud"); viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud"); while (g_run.load() && !viewer->wasStopped()) { // 1) pega a nuvem já acumulada (engine) CloudRGB src = g_mode_map.load() ? Core::get_cloud_map() : Core::get_cloud_live(); // 2) converte para PCL pcl::PointCloud::Ptr pc_now(new pcl::PointCloud); pc_now->points.reserve(src.pts.size()); for (const auto& q : src.pts) { pcl::PointXYZRGB p; p.x = q.x; p.y = q.y; p.z = q.z; p.r = q.r; p.g = q.g; p.b = q.b; // vamos recolorir abaixo pc_now->points.push_back(p); } pc_now->width = (uint32_t)pc_now->points.size(); pc_now->height = 1; pc_now->is_dense = true; // 3) colorir por distância (default) ou por altura (tecla D alterna) bool by_dist = g_color_by_distance.load(); for (auto& p : pc_now->points) { colorizePoint(p, by_dist); // mesma função já existente aí } // 4) atualiza no viewer pcl::visualization::PointCloudColorHandlerRGBField rgb2(pc_now); viewer->updatePointCloud(pc_now, rgb2, "cloud"); addHudTop(viewer); addHudBottom(viewer); if (g_show_axis.load()) { drawDistances3D(viewer); } else { // remove tudo se estiver OFF for (auto id : { "linha_frente","linha_tras","linha_esquerda","linha_direita","linha_cima","linha_baixo", "texto_frente","texto_tras","texto_esquerda","texto_direita","texto_cima","texto_baixo" }) viewer->removeShape(id); } if (g_draw_boxes.load()) { uint64_t now_ms = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); draw_bboxes_one_actor(viewer, now_ms, /*min_interval_ms=*/10, /*max_boxes=*/20); } else { viewer->removeShape(g_actor_id); g_last_n = 0; } viewer->spinOnce(15); std::this_thread::sleep_for(std::chrono::milliseconds(15)); } } } // anon namespace ViewerDebug { bool start(bool usar_slam_inicial) { if (g_run.exchange(true)) return true; g_use_slam = usar_slam_inicial; try { g_thr = std::thread(loop); return true; } catch (...) { g_run.store(false); return false; } } void stop() { if (!g_run.exchange(false)) return; if (g_thr.joinable()) g_thr.join(); } void set_draw_boxes(bool on) { g_draw_boxes = on; } void set_color_by_distance(bool on) { g_color_by_distance = on; } void set_mode_map(bool on) { g_mode_map = on; } void set_use_slam(bool on) { g_use_slam = on; } bool get_mode_map() { return g_mode_map.load(); } } // namespace ViewerDebug