/*************************************************************************** file : steering4.cpp created : September 2026 copyright : (C) 2026 by Bernhard Wymann email : berniw@bluewin.ch ***************************************************************************/ /* This program is free software; you can redistribute it and/or modify it * under the GNU General Public License, version 2 or (at your option) any * later version. Distributed WITHOUT ANY WARRANTY, including MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See . */ #include #include #include #include #include "steering4.h" // Compare the baseline first, then change one setting at a time. static constexpr bool USE_LOOKAHEAD = true; static constexpr double LOOKAHEAD_BASE = 8.0; // metres static constexpr double LOOKAHEAD_TIME = 0.33; // seconds static constexpr bool ENABLE_EDGE_FEEDBACK = true; static constexpr double EDGE_MARGIN = 1.0; // metres from the car's corners static constexpr double REJOIN_SPEED = 5.0; // m/s at or beyond a road edge double segmentProgress(const tTrkLocPos& position) { const tTrackSeg* segment = position.seg; const double distance = segment->type == TR_STR ? position.toStart : position.toStart * segment->radius; return std::max(0.0, std::min(double(segment->length), distance)); } Vector2 centerlinePoint(const tTrackSeg* segment, double distance) { const Vector2 start{(segment->vertex[TR_SL].x + segment->vertex[TR_SR].x) / 2.0, (segment->vertex[TR_SL].y + segment->vertex[TR_SR].y) / 2.0}; const double along = std::max(0.0, std::min(double(segment->length), distance)); if (segment->type == TR_STR) { const Vector2 end{(segment->vertex[TR_EL].x + segment->vertex[TR_ER].x) / 2.0, (segment->vertex[TR_EL].y + segment->vertex[TR_ER].y) / 2.0}; return segment->length > 0.0 ? start + (end - start) * (along / segment->length) : start; } const Vector2 center{segment->center.x, segment->center.y}; const double sign = segment->type == TR_LFT ? 1.0 : -1.0; return segment->radius > 0.0 ? start.rotated(center, sign * along / segment->radius) : start; } bool targetAhead(const tTrkLocPos& position, double distance, int segmentCount, Vector2& target) { const tTrackSeg* segment = position.seg; if (!segment || !std::isfinite(distance) || distance < 0.0) { return false; } double along = segmentProgress(position) + distance; // One complete lap may finish in the starting segment: allow nseg + 1 visits. for (int visited = 0; visited <= segmentCount && segment; ++visited) { if (segment->length > 0.0 && along <= segment->length) { target = centerlinePoint(segment, along); return true; } along -= std::max(0.0, double(segment->length)); segment = segment->next; } return false; } double steerToward(const tCarElt* car, const Vector2& target) { const Vector2 direction = target - Vector2{car->_pos_X, car->_pos_Y}; if (direction.length() < 1e-6 || car->_steerLock <= 0.0) { return 0.0; } double angle = std::atan2(direction.y, direction.x) - car->_yaw; NORM_PI_PI(angle); return std::max(-1.0, std::min(1.0, angle / car->_steerLock)); } double chooseSteering(tCarElt* car, int segmentCount, double trackLength) { const double requested = LOOKAHEAD_BASE + LOOKAHEAD_TIME * std::max(0.0, double(car->_speed_x)); const double lookahead = std::min(std::max(0.0, requested), std::max(0.0, trackLength * 0.5)); Vector2 target; if (USE_LOOKAHEAD && targetAhead(car->_trkPos, lookahead, segmentCount, target)) { return steerToward(car, target); } double angle = RtTrackSideTgAngleL(&car->_trkPos) - car->_yaw; NORM_PI_PI(angle); angle -= car->_trkPos.toMiddle / std::max(1.0, double(car->_trkPos.seg->width)); return car->_steerLock > 0.0 ? std::max(-1.0, std::min(1.0, angle / car->_steerLock)) : 0.0; } double roadClearance(tCarElt* car) { double clearance = std::numeric_limits::infinity(); for (int i = 0; i < 4; ++i) { tTrkLocPos corner{}; // MAIN measures against the racing road; TRACK includes sides up to barriers. RtTrackGlobal2Local(car->_trkPos.seg, car->_corner_x(i), car->_corner_y(i), &corner, TR_LPOS_MAIN); clearance = std::min(clearance, double(std::min(corner.toLeft, corner.toRight))); } return clearance; } double edgeSpeedLimit(tCarElt* car, double targetSpeed) { if (!ENABLE_EDGE_FEEDBACK) { return targetSpeed; } const double fraction = std::max(0.0, std::min(1.0, roadClearance(car) / EDGE_MARGIN)); const double slow = std::min(targetSpeed, REJOIN_SPEED); return slow + fraction * (targetSpeed - slow); } double experimentalRadius(const tTrackSeg* segment, double progress, int segmentCount) { if (segment->type == TR_STR || segment->radius <= 0.0) { return segment->radius; } double arc = std::max(0.0, double(segment->arc) - progress / segment->radius); const tTrackSeg* next = segment->next; for (int i = 1; i < segmentCount && next && next != segment && next->type == segment->type && arc < PI / 2.0; ++i, next = next->next) { arc += std::max(0.0, double(next->arc)); } const double fraction = std::max(0.05, std::min(1.0, arc / (PI / 2.0))); const double estimate = (segment->radius + segment->width / 2.0) / std::sqrt(fraction); // An experiment, not a measured path radius. Limit its optimistic speed boost. return std::min(1.5 * segment->radius, estimate); }