telemetry: emit track geometry (DriveGraph nodes) in RaceStarted

Walk the main driveline graph at race start and populate
TrackGeometry with center, left/right edge, width, and
distance_from_start for each DriveNode quad. Includes bounding
box and start position/heading for 2D minimap rendering.
This commit is contained in:
Benjamin
2026-06-11 21:27:09 +02:00
parent d61372c2d2
commit 961ca07bcf
+103
View File
@@ -19,9 +19,12 @@
#include "tracks/track.hpp" #include "tracks/track.hpp"
#include "tracks/track_sector.hpp" #include "tracks/track_sector.hpp"
#include "tracks/check_manager.hpp" #include "tracks/check_manager.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "utils/log.hpp" #include "utils/log.hpp"
#include "utils/string_utils.hpp" #include "utils/string_utils.hpp"
#include <algorithm>
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
#include <random> #include <random>
@@ -176,6 +179,106 @@ void TelemetryEmitter::onRaceStarted()
} }
} }
// --- Track geometry (for 2D minimap / replay) ---
DriveGraph* graph = DriveGraph::get();
if (graph != nullptr && graph->getNumNodes() > 0)
{
auto* geom = ev->mutable_track_geometry();
float min_x = 1e9f, min_y = 1e9f, min_z = 1e9f;
float max_x = -1e9f, max_y = -1e9f, max_z = -1e9f;
// Walk the main driveline loop following successor 0.
unsigned int num_nodes = graph->getNumNodes();
unsigned int current = 0;
unsigned int visited = 0;
do {
const DriveNode* dn = graph->getNode(current);
if (dn == nullptr) break;
auto* node = geom->add_nodes();
// Center
const Vec3& center = dn->getCenter();
auto* c = node->mutable_center();
c->set_x(center.x());
c->set_y(center.y());
c->set_z(center.z());
// Width
node->set_width(dn->getPathWidth());
// Distance from start
node->set_distance_from_start(dn->getDistanceFromStart());
// Left and right edge points from Quad corners.
// Quad layout: [0]=lower-right, [1]=lower-left, [2]=upper-left, [3]=upper-right
const Vec3& lr = (*dn)[0];
const Vec3& ll = (*dn)[1];
const Vec3& ul = (*dn)[2];
const Vec3& ur = (*dn)[3];
// Left edge = midpoint of lower-left and upper-left
auto* left = node->mutable_left_edge();
left->set_x((ll.x() + ul.x()) * 0.5f);
left->set_y((ll.y() + ul.y()) * 0.5f);
left->set_z((ll.z() + ul.z()) * 0.5f);
// Right edge = midpoint of lower-right and upper-right
auto* right = node->mutable_right_edge();
right->set_x((lr.x() + ur.x()) * 0.5f);
right->set_y((lr.y() + ur.y()) * 0.5f);
right->set_z((lr.z() + ur.z()) * 0.5f);
// Update bounding box
min_x = std::min(min_x, center.x());
min_y = std::min(min_y, center.y());
min_z = std::min(min_z, center.z());
max_x = std::max(max_x, center.x());
max_y = std::max(max_y, center.y());
max_z = std::max(max_z, center.z());
// Follow successor 0 (main driveline)
if (dn->getNumberOfSuccessors() > 0)
current = dn->getSuccessor(0);
else
break;
visited++;
} while (current != 0 && visited < num_nodes);
// Bounding box
auto* bmin = geom->mutable_bbox_min();
bmin->set_x(min_x);
bmin->set_y(min_y);
bmin->set_z(min_z);
auto* bmax = geom->mutable_bbox_max();
bmax->set_x(max_x);
bmax->set_y(max_y);
bmax->set_z(max_z);
// Start position + heading
const DriveNode* start_node = graph->getNode(0);
if (start_node != nullptr)
{
auto* sp = geom->mutable_start_position();
sp->set_x(start_node->getCenter().x());
sp->set_y(start_node->getCenter().y());
sp->set_z(start_node->getCenter().z());
// Heading from node 0 toward its successor
if (start_node->getNumberOfSuccessors() > 0)
{
geom->set_start_heading(
start_node->getAngleToSuccessor(0));
}
}
Log::info("Telemetry", "Track geometry: %u nodes, bbox=(%.0f,%.0f)-(%.0f,%.0f)",
visited, min_x, min_z, max_x, max_z);
}
sendFrame(frame); sendFrame(frame);
Log::info("Telemetry", "Race started: game_id=%s track=%s karts=%d", Log::info("Telemetry", "Race started: game_id=%s track=%s karts=%d",
m_game_id.c_str(), rm->getTrackName().c_str(), num_karts); m_game_id.c_str(), rm->getTrackName().c_str(), num_karts);