diff --git a/rmcs_ws/src/rmcs_bringup/config/flight.yaml b/rmcs_ws/src/rmcs_bringup/config/flight.yaml
index 2291e12a..d0082799 100644
--- a/rmcs_ws/src/rmcs_bringup/config/flight.yaml
+++ b/rmcs_ws/src/rmcs_bringup/config/flight.yaml
@@ -21,6 +21,7 @@ rmcs_executor:
- rmcs_core::referee::Status -> referee_status
- rmcs_core::referee::command::interaction::Ui -> referee_ui
- rmcs_core::referee::app::ui::Flight -> referee_ui_flight
+ - rmcs_core::referee::app::ui::AutoAimUi -> auto_aim_ui
# - rmcs_core::broadcaster::ValueBroadcaster -> value_broadcaster
# - rmcs_core::broadcaster::TfBroadcaster -> tf_broadcaster
@@ -151,3 +152,9 @@ bullet_feeder_velocity_pid_controller:
kp: 0.583
ki: 0.0
kd: 0.0
+
+auto_aim_ui:
+ ros__parameters:
+ offset_x: 0.000
+ offset_y: 0.000
+ offset_z: 0.000
diff --git a/rmcs_ws/src/rmcs_core/plugins.xml b/rmcs_ws/src/rmcs_core/plugins.xml
index 1f46763e..3ac3bf0c 100644
--- a/rmcs_ws/src/rmcs_core/plugins.xml
+++ b/rmcs_ws/src/rmcs_core/plugins.xml
@@ -56,4 +56,5 @@
+
diff --git a/rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp b/rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp
new file mode 100644
index 00000000..b874a165
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp
@@ -0,0 +1,183 @@
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "referee/app/ui/shape/shape.hpp"
+
+namespace rmcs_core::referee::app::ui {
+using namespace rmcs_description;
+
+class AutoAimUi
+ : public rmcs_executor::Component
+ , public rclcpp::Node {
+public:
+ AutoAimUi()
+ : Node{
+ get_component_name(),
+ rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true)} {
+
+ offset_ = Eigen::Vector3d{
+ get_parameter_or("offset_x", 0.),
+ get_parameter_or("offset_y", 0.),
+ get_parameter_or("offset_z", 0.),
+ };
+
+ // What's the point of an auto-aim UI if it doesn't have auto-aim?
+ register_input("/tf", tf_, true);
+ register_input("/auto_aim/robot_center", robot_center_, true);
+ register_input("/auto_aim/should_shoot", should_shoot_, true);
+ }
+
+ void update() override {
+ if (!robot_center_->allFinite() || robot_center_->isZero()) {
+ hide_all();
+ return;
+ }
+
+ const auto point = reproject(*robot_center_);
+ if (!point.allFinite() //
+ || point.x() >= kScreenW || point.x() < 0 //
+ || point.y() >= kScreenH || point.y() < 0 //
+ ) {
+ hide_all();
+ return;
+ }
+
+ const auto x = std::clamp(std::lround(point.x()), 0, kScreenW - 1);
+ const auto y = std::clamp(std::lround(point.y()), 0, kScreenH - 1);
+
+ const auto color = *should_shoot_ ? Shape::Color::ORANGE : Shape::Color::GREEN;
+ const auto radius = *should_shoot_ ? 10 : 15;
+
+ center_ring_.set_color(color);
+ center_ring_.set_x(x);
+ center_ring_.set_y(y);
+ center_ring_.set_r(radius);
+ center_ring_.set_visible(true);
+
+ if (*should_shoot_) {
+ constexpr int kCrossLen = 16;
+ constexpr int kCrossGap = 5;
+
+ constexpr auto clamp_x = [](int x) {
+ return std::clamp(x, 0, kScreenW - 1);
+ };
+ constexpr auto clamp_y = [](int y) {
+ return std::clamp(y, 0, kScreenH - 1);
+ };
+
+ cross_top_.set_color(color);
+ cross_top_.set_x(x);
+ cross_top_.set_y(clamp_y(y - kCrossLen));
+ cross_top_.set_x2(x);
+ cross_top_.set_y2(clamp_y(y - kCrossGap));
+ cross_top_.set_visible(true);
+
+ cross_bottom_.set_color(color);
+ cross_bottom_.set_x(x);
+ cross_bottom_.set_y(clamp_y(y + kCrossGap));
+ cross_bottom_.set_x2(x);
+ cross_bottom_.set_y2(clamp_y(y + kCrossLen));
+ cross_bottom_.set_visible(true);
+
+ cross_left_.set_color(color);
+ cross_left_.set_x(clamp_x(x - kCrossLen));
+ cross_left_.set_y(y);
+ cross_left_.set_x2(clamp_x(x - kCrossGap));
+ cross_left_.set_y2(y);
+ cross_left_.set_visible(true);
+
+ cross_right_.set_color(color);
+ cross_right_.set_x(clamp_x(x + kCrossGap));
+ cross_right_.set_y(y);
+ cross_right_.set_x2(clamp_x(x + kCrossLen));
+ cross_right_.set_y2(y);
+ cross_right_.set_visible(true);
+ } else {
+ cross_top_.set_visible(false);
+ cross_bottom_.set_visible(false);
+ cross_left_.set_visible(false);
+ cross_right_.set_visible(false);
+ }
+ }
+
+private:
+ static constexpr std::uint16_t kScreenW = 1920;
+ static constexpr std::uint16_t kScreenH = 1080;
+
+ static constexpr double kFx = 730.7267062695;
+ static constexpr double kFy = 730.5886055073;
+ static constexpr double kCx = 961.8345772991;
+ static constexpr double kCy = 549.3357457590;
+
+ static constexpr double kK1 = -0.1764932263;
+ static constexpr double kK2 = +0.1582678597;
+ static constexpr double kK3 = -0.1557993057;
+ static constexpr double kP1 = -0.0000433367;
+ static constexpr double kP2 = +0.0003937540;
+
+ Eigen::Vector3d offset_ = Eigen::Vector3d::Zero();
+
+ InputInterface tf_;
+ InputInterface robot_center_;
+ InputInterface should_shoot_;
+
+ Circle center_ring_{Shape::Color::GREEN, 2, 0, 0, 5, 5, false};
+ Line cross_top_{Shape::Color::GREEN, 2, 0, 0, 0, 0, false};
+ Line cross_bottom_{Shape::Color::GREEN, 2, 0, 0, 0, 0, false};
+ Line cross_left_{Shape::Color::GREEN, 2, 0, 0, 0, 0, false};
+ Line cross_right_{Shape::Color::GREEN, 2, 0, 0, 0, 0, false};
+
+ void hide_all() {
+ center_ring_.set_visible(false);
+ cross_top_.set_visible(false);
+ cross_bottom_.set_visible(false);
+ cross_left_.set_visible(false);
+ cross_right_.set_visible(false);
+ }
+
+ Eigen::Vector2d reproject(const Eigen::Vector3d& center) const {
+ const auto camera_pose = fast_tf::lookup_transform(*tf_);
+
+ const auto q_aa = Eigen::Quaterniond{camera_pose.rotation()};
+ const auto t_aa = Eigen::Vector3d{camera_pose.translation()};
+
+ const auto t_ui = Eigen::Vector3d{t_aa - q_aa * offset_};
+
+ const auto point_ros = Eigen::Vector3d{q_aa.inverse() * (center - t_ui)};
+
+ const double x_cv = -point_ros.y();
+ const double y_cv = +point_ros.z();
+ const double z_cv = +point_ros.x();
+
+ if (z_cv <= 1e-6)
+ return Eigen::Vector2d{
+ std::numeric_limits::quiet_NaN(),
+ std::numeric_limits::quiet_NaN(),
+ };
+
+ const double x_norm = x_cv / z_cv;
+ const double y_norm = y_cv / z_cv;
+
+ const double r2 = x_norm * x_norm + y_norm * y_norm;
+ const double r4 = r2 * r2;
+ const double r6 = r4 * r2;
+
+ const double radial = 1.0 + kK1 * r2 + kK2 * r4 + kK3 * r6;
+ const double x_dist =
+ x_norm * radial + 2.0 * kP1 * x_norm * y_norm + kP2 * (r2 + 2.0 * x_norm * x_norm);
+ const double y_dist =
+ y_norm * radial + kP1 * (r2 + 2.0 * y_norm * y_norm) + 2.0 * kP2 * x_norm * y_norm;
+
+ return Eigen::Vector2d{kFx * x_dist + kCx, kFy * y_dist + kCy};
+ }
+};
+
+} // namespace rmcs_core::referee::app::ui
+
+#include
+PLUGINLIB_EXPORT_CLASS(rmcs_core::referee::app::ui::AutoAimUi, rmcs_executor::Component)
diff --git a/rmcs_ws/src/rmcs_executor/CMakeLists.txt b/rmcs_ws/src/rmcs_executor/CMakeLists.txt
index ec21edbf..8f255f45 100644
--- a/rmcs_ws/src/rmcs_executor/CMakeLists.txt
+++ b/rmcs_ws/src/rmcs_executor/CMakeLists.txt
@@ -3,7 +3,8 @@ project(rmcs_executor)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
+set(CMAKE_CXX_STANDARD 23)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-g1 -rdynamic -O2 -Wall -Wextra -Wpedantic)
diff --git a/rmcs_ws/src/rmcs_executor/src/executor.hpp b/rmcs_ws/src/rmcs_executor/src/executor.hpp
index eb454fd0..751d4dfd 100644
--- a/rmcs_ws/src/rmcs_executor/src/executor.hpp
+++ b/rmcs_ws/src/rmcs_executor/src/executor.hpp
@@ -1,10 +1,18 @@
#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
#include