diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ce5be9b8..1aa30e45f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
## Latest
+ * Add build variant with AD RSS library integration with RSS sensor and result visualisation
* Added new sensor: Inertial measurement unit (IMU)
* Moved GNSS sensor from client to server side
* Now all the camera-based sensors are provided with an additional parametrized lens distortion shader
diff --git a/Docs/img/rss_carla_integration.png b/Docs/img/rss_carla_integration.png
new file mode 100644
index 000000000..7c8cab24a
Binary files /dev/null and b/Docs/img/rss_carla_integration.png differ
diff --git a/Docs/rss_lib_integration.md b/Docs/rss_lib_integration.md
new file mode 100644
index 000000000..6b219a67b
--- /dev/null
+++ b/Docs/rss_lib_integration.md
@@ -0,0 +1,42 @@
+
AD Responsibility Sensitive Safety model (RSS) integration
+
+> _This feature is a work in progress, only a Linux build variant is available._
+
+This feature integrates the [C++ Library for Responsibility Sensitive Safety](https://github.com/intel/ad-rss-lib) into the CARLA Client library.
+
+**As the _ad-rss-lib_ library is licensed under LGPL-2.1-only, building the variant which includes this feature and therefor the library might have some implications to the outgoing license of the resulting binary!**
+
+It provides basic implementations of both an **RssSensor**, the situation analysis and response generation by the **ad-rss-lib** and an basic **RssRestrictor** class which applies the restrictions to given vehicle commands.
+
+The **RssSensor** results can be visualized within CARLA.
+[![RSS safety sensor in CARLA](img/rss_carla_integration.png)](https://www.youtube.com/watch?v=UxKPXPT2T8Q)
+
+
+Please see [C++ Library for Responsibility Sensitive Safety documentation](https://intel.github.io/ad-rss-lib/) and especially the [Background documentation](https://intel.github.io/ad-rss-lib/documentation/Main.html) for further details.
+
+
+Compilation
+
+RSS integration is a Linux-only build variant.
+Please see [Build System](dev/build_system.md) for general information.
+*LibCarla* with RSS has the be explicitly compiled by
+
+```sh
+make LibCarla.client.rss
+```
+
+The *PythonAPI* with RSS is built by
+
+```sh
+make PythonAPI.rss
+```
+
+
+Current state
+RssSensor
+The RssSensor is currently only considering vehicles within the same road segment, but on all lanes within that segment. Intersections are not yet supported!
+
+RssRestrictor
+The current implementation of the RssRestrictor checks and potentially modifies a given *VehicleControl* generated by e.g. and Automated Driving stack or user imput via a *manual_control* client (see the *PythonAPI/examples/manual_control_rss.py*).
+
+Due to the structure of *VehicleControl* (just throttle, brake, streering values for the car under control), the Restrictor modifies and sets these values to best reach the desired accelerations or decelerations by a given restriction. Due to car physics and the simple control options these might not be met.
diff --git a/LibCarla/cmake/client/CMakeLists.txt b/LibCarla/cmake/client/CMakeLists.txt
index 5c14a44d4..96a39803c 100644
--- a/LibCarla/cmake/client/CMakeLists.txt
+++ b/LibCarla/cmake/client/CMakeLists.txt
@@ -1,6 +1,19 @@
cmake_minimum_required(VERSION 3.5.1)
project(libcarla-client)
+option (BUILD_RSS_VARIANT "Enables ad-rss-lib based RSS components" OFF)
+
+if (BUILD_RSS_VARIANT)
+ find_package(ad-rss-lib CONFIG REQUIRED)
+ get_target_property(ad-rss-lib_file ad-rss-lib LOCATION)
+ install(FILES ${ad-rss-lib_file} DESTINATION lib)
+ get_target_property(ad-rss-lib_include_dirs ad-rss-lib INTERFACE_INCLUDE_DIRECTORIES)
+ foreach(dir ${ad-rss-lib_include_dirs})
+ file(GLOB ad-rss-lib_includes "${dir}/*")
+ install(DIRECTORY ${ad-rss-lib_includes} DESTINATION include/system)
+ endforeach()
+endif()
+
# Install Recast&Detour libraries
install(DIRECTORY "${RECAST_INCLUDE_PATH}/recast" DESTINATION include/system)
file(GLOB libcarla_carla_recastlib "${RECAST_LIB_PATH}/*.*")
@@ -129,6 +142,14 @@ file(GLOB libcarla_carla_rpc_sources
set(libcarla_sources "${libcarla_sources};${libcarla_carla_rpc_sources}")
install(FILES ${libcarla_carla_rpc_sources} DESTINATION include/carla/rpc)
+if (BUILD_RSS_VARIANT)
+ file(GLOB libcarla_carla_rss_sources
+ "${libcarla_source_path}/carla/rss/*.cpp"
+ "${libcarla_source_path}/carla/rss/*.h")
+ set(libcarla_sources "${libcarla_sources};${libcarla_carla_rss_sources}")
+ install(FILES ${libcarla_carla_rss_sources} DESTINATION include/carla/rss)
+endif()
+
file(GLOB libcarla_carla_sensor_sources
"${libcarla_source_path}/carla/sensor/*.cpp"
"${libcarla_source_path}/carla/sensor/*.h")
@@ -192,6 +213,7 @@ install(FILES ${libcarla_pugixml_sources} DESTINATION include/pugixml)
# ==============================================================================
# Create targets for debug and release in the same build type.
# ==============================================================================
+
if (LIBCARLA_BUILD_RELEASE)
add_library(carla_client STATIC ${libcarla_sources})
@@ -201,6 +223,11 @@ if (LIBCARLA_BUILD_RELEASE)
"${RPCLIB_INCLUDE_PATH}"
"${RECAST_INCLUDE_PATH}")
+ if (BUILD_RSS_VARIANT)
+ target_compile_definitions(carla_client PRIVATE RSS_ENABLED)
+ target_link_libraries(carla_client ad-rss-lib)
+ endif()
+
install(TARGETS carla_client DESTINATION lib)
if (WIN32) # @todo Fix PythonAPI build on Windows.
@@ -233,6 +260,11 @@ if (LIBCARLA_BUILD_DEBUG)
"${RPCLIB_INCLUDE_PATH}"
"${RECAST_INCLUDE_PATH}")
+ if (BUILD_RSS_VARIANT)
+ target_compile_definitions(carla_client_debug PRIVATE RSS_ENABLED)
+ target_link_libraries(carla_client_debug ad-rss-lib)
+ endif()
+
install(TARGETS carla_client_debug DESTINATION lib)
if (WIN32) # @todo Fix PythonAPI build on Windows.
diff --git a/LibCarla/source/carla/client/detail/ActorFactory.cpp b/LibCarla/source/carla/client/detail/ActorFactory.cpp
index 4f71979f9..4a43e1851 100644
--- a/LibCarla/source/carla/client/detail/ActorFactory.cpp
+++ b/LibCarla/source/carla/client/detail/ActorFactory.cpp
@@ -11,6 +11,9 @@
#include "carla/client/Actor.h"
#include "carla/client/LaneInvasionSensor.h"
#include "carla/client/ServerSideSensor.h"
+#ifdef RSS_ENABLED
+#include "carla/rss/RssSensor.h"
+#endif
#include "carla/client/TrafficLight.h"
#include "carla/client/TrafficSign.h"
#include "carla/client/Vehicle.h"
@@ -74,6 +77,10 @@ namespace detail {
auto init = ActorInitializer{description, episode};
if (description.description.id == "sensor.other.lane_invasion") {
return MakeActorImpl(std::move(init), gc);
+#ifdef RSS_ENABLED
+ } else if (description.description.id == "sensor.other.rss") {
+ return MakeActorImpl(std::move(init), gc);
+#endif
} else if (description.HasAStream()) {
return MakeActorImpl(std::move(init), gc);
} else if (StringUtil::StartsWith(description.description.id, "vehicle.")) {
diff --git a/LibCarla/source/carla/rss/RssCheck.cpp b/LibCarla/source/carla/rss/RssCheck.cpp
new file mode 100644
index 000000000..ea881b2fd
--- /dev/null
+++ b/LibCarla/source/carla/rss/RssCheck.cpp
@@ -0,0 +1,465 @@
+// Copyright (c) 2019 Intel Corporation
+//
+// This work is licensed under the terms of the MIT license.
+// For a copy, see .
+
+#include "carla/rss/RssCheck.h"
+
+#include "carla/client/Map.h"
+#include "carla/client/Vehicle.h"
+#include "carla/client/Waypoint.h"
+#include "carla/road/element/RoadInfoLaneWidth.h"
+
+#include "ad_rss/core/RssResponseResolving.hpp"
+#include "ad_rss/core/RssResponseTransformation.hpp"
+#include "ad_rss/core/RssSituationChecking.hpp"
+#include "ad_rss/core/RssSituationExtraction.hpp"
+#include "ad_rss/state/RssStateOperation.hpp"
+
+#include
+#include
+#include
+
+namespace carla {
+namespace rss {
+
+ namespace csd = carla::sensor::data;
+
+// constants for deg-> rad conversion PI / 180
+ constexpr float toRadians = static_cast(M_PI) / 180.0f;
+
+ inline float calculateAngleDelta(const float angle1, const float angle2) {
+ float delta = angle1 - angle2;
+ delta -= std::floor((delta + 180.f) / 360.f) * 360.f;
+ return delta;
+ }
+
+ RssCheck::RssCheck() {
+ _rssSituationExtraction = std::make_shared<::ad_rss::core::RssSituationExtraction>();
+ _rssSituationChecking = std::make_shared<::ad_rss::core::RssSituationChecking>();
+ _rssResponseResolving = std::make_shared<::ad_rss::core::RssResponseResolving>();
+
+ _egoVehicleDynamics = std::make_shared<::ad_rss::world::RssDynamics>();
+ _otherVehicleDynamics = std::make_shared<::ad_rss::world::RssDynamics>();
+
+ ::ad_rss::world::RssDynamics defaultDynamics;
+
+ defaultDynamics.alphaLon.accelMax = ::ad_rss::physics::Acceleration(3.5);
+ defaultDynamics.alphaLon.brakeMax = ::ad_rss::physics::Acceleration(8.);
+ defaultDynamics.alphaLon.brakeMin = ::ad_rss::physics::Acceleration(4.);
+ defaultDynamics.alphaLon.brakeMinCorrect = ::ad_rss::physics::Acceleration(3);
+ defaultDynamics.alphaLat.accelMax = ::ad_rss::physics::Acceleration(0.2);
+ defaultDynamics.alphaLat.brakeMin = ::ad_rss::physics::Acceleration(0.8);
+ defaultDynamics.responseTime = ::ad_rss::physics::Duration(1.);
+
+ *_egoVehicleDynamics = defaultDynamics;
+ *_otherVehicleDynamics = defaultDynamics;
+ }
+
+ RssCheck::~RssCheck() = default;
+
+ const ::ad_rss::world::RssDynamics &RssCheck::getEgoVehicleDynamics() const {
+ return *_egoVehicleDynamics;
+ }
+
+ void RssCheck::setEgoVehicleDynamics(const ::ad_rss::world::RssDynamics &dynamics) {
+ *_egoVehicleDynamics = dynamics;
+ }
+
+ const ::ad_rss::world::RssDynamics &RssCheck::getOtherVehicleDynamics() const {
+ return *_otherVehicleDynamics;
+ }
+
+ void RssCheck::setOtherVehicleDynamics(const ::ad_rss::world::RssDynamics &dynamics) {
+ *_otherVehicleDynamics = dynamics;
+ }
+
+ bool RssCheck::checkObjects(cc::Timestamp const & timestamp, cc::World &world,
+ carla::SharedPtr const & vehicles,
+ carla::SharedPtr const & carlaEgoActor,
+ carla::SharedPtr const & clientMap,
+ ::ad_rss::state::ProperResponse &response,
+ ::ad_rss::world::AccelerationRestriction &accelerationRestriction,
+ ::ad_rss::world::Velocity &rssEgoVelocity, bool visualizeResults) {
+ const auto carlaEgoVehicle = boost::dynamic_pointer_cast(carlaEgoActor);
+
+ ::ad_rss::world::WorldModel worldModel;
+ worldModel.timeIndex = timestamp.frame;
+
+ ::ad_rss::world::Object egoVehicle;
+ ::ad_rss::world::RssDynamics egoDynamics;
+ initVehicle(egoVehicle);
+ initEgoVehicleDynamics(egoDynamics);
+ egoVehicle.objectId = carlaEgoVehicle->GetId();
+ egoVehicle.objectType = ::ad_rss::world::ObjectType::EgoVehicle;
+
+ cg::Location egoVehicleLocation = carlaEgoVehicle->GetLocation();
+ auto egoClientWaypoint = clientMap->GetWaypoint(egoVehicleLocation, false);
+
+ if (egoClientWaypoint != nullptr) {
+ // ego vehicle is located on a lane marked for driving
+ auto egoWaypointLoc = egoClientWaypoint->GetTransform();
+ auto egoLaneId = egoClientWaypoint->GetLaneId();
+ auto egoVelocity = carlaEgoVehicle->GetVelocity();
+ auto egoVehicleTransform = carlaEgoVehicle->GetTransform();
+
+ // calculate driving direction
+ auto yawDiff = calculateAngleDelta(egoWaypointLoc.rotation.yaw, egoVehicleTransform.rotation.yaw);
+
+ bool drivingInLaneDirection = true;
+ if (std::abs(yawDiff) > 45.f) {
+ drivingInLaneDirection = false;
+ }
+
+ // calculate road direction
+ bool drivingInRoadDirection = (egoLaneId > 0) ^ drivingInLaneDirection;
+
+ auto &carlaRoadMap = clientMap->GetMap();
+ auto egoWaypoint = carlaRoadMap.GetWaypoint(egoVehicleLocation);
+ auto &egoLane = carlaRoadMap.GetLane(*egoWaypoint);
+ auto egoRoad = egoLane.GetRoad();
+
+ ::ad_rss::world::RoadArea roadArea;
+ ::ad_rss::world::RoadSegment roadSegment;
+
+ // generate road area
+ for (auto &laneSection : egoRoad->GetLaneSections()) {
+ for (const auto &pair : laneSection.GetLanes()) {
+ const auto &lane = pair.second;
+ if ((static_cast(lane.GetType()) &
+ static_cast(carla::road::Lane::LaneType::Driving)) > 0) {
+ ::ad_rss::world::LaneSegment laneSegment;
+ // assumption: only one segment per road
+
+ auto laneLength = lane.GetLength();
+ // evaluate width at lane start
+ double pos = laneSection.GetDistance();
+ const auto lane_width_info = lane.GetInfo(pos);
+ double laneWidth = 0.0;
+ if (lane_width_info != nullptr) {
+ laneWidth = lane_width_info->GetPolynomial().Evaluate(pos);
+ }
+
+ convertAndSetLaneSegmentId(lane, laneSegment);
+ if ((lane.GetId() < 0) ^ drivingInRoadDirection) {
+ laneSegment.drivingDirection = ::ad_rss::world::LaneDrivingDirection::Negative;
+ } else {
+ laneSegment.drivingDirection = ::ad_rss::world::LaneDrivingDirection::Positive;
+ }
+
+ // lane segment is assumed to be strait and evenly wide, so minimum
+ // and maximum length and width are set to the same values
+ laneSegment.length.minimum = ::ad_rss::physics::Distance(laneLength);
+ laneSegment.length.maximum = laneSegment.length.minimum;
+ laneSegment.width.minimum = ::ad_rss::physics::Distance(laneWidth);
+ laneSegment.width.maximum = laneSegment.width.minimum;
+
+ roadSegment.push_back(laneSegment);
+ }
+ }
+ std::sort(
+ roadSegment.begin(), roadSegment.end(),
+ [&drivingInRoadDirection](::ad_rss::world::LaneSegment const &f,
+ ::ad_rss::world::LaneSegment const &s) {
+ return (f.id <= s.id) ^ drivingInRoadDirection;
+ });
+ roadArea.push_back(roadSegment);
+ }
+
+ calculateLatLonVelocities(egoWaypointLoc, egoVelocity, egoVehicle, !drivingInLaneDirection);
+ rssEgoVelocity = egoVehicle.velocity;
+
+ auto egoBounds = getVehicleBounds(*carlaEgoVehicle);
+
+ calculateOccupiedRegions(egoBounds, egoVehicle.occupiedRegions, carlaRoadMap, drivingInRoadDirection,
+ egoVehicleTransform);
+
+ for (const auto &actor : *vehicles) {
+ const auto vehicle = boost::dynamic_pointer_cast(actor);
+ if (vehicle == nullptr) {
+ continue;
+ }
+ if (vehicle->GetId() == carlaEgoVehicle->GetId()) {
+ continue;
+ }
+ ::ad_rss::world::Object otherVehicle;
+ ::ad_rss::world::RssDynamics otherDynamics;
+ initVehicle(otherVehicle);
+ initOtherVehicleDynamics(otherDynamics);
+ otherVehicle.objectId = vehicle->GetId();
+ otherVehicle.objectType = ::ad_rss::world::ObjectType::OtherVehicle;
+
+ auto otherVehicleLoc = vehicle->GetLocation();
+ auto otherVehicleClientWaypoint = clientMap->GetWaypoint(otherVehicleLoc);
+
+ // same road
+ if (egoClientWaypoint->GetRoadId() == otherVehicleClientWaypoint->GetRoadId()) {
+ auto otherVehicleTransform = vehicle->GetTransform();
+ auto otherBounds = getVehicleBounds(*vehicle);
+ auto otherWaypointLoc = otherVehicleClientWaypoint->GetTransform();
+ auto otherVelocity = vehicle->GetVelocity();
+ calculateLatLonVelocities(otherWaypointLoc, otherVelocity, otherVehicle, false);
+
+ ::ad_rss::world::Scene scene;
+
+ calculateOccupiedRegions(otherBounds, otherVehicle.occupiedRegions, carlaRoadMap, drivingInRoadDirection,
+ otherVehicleTransform);
+
+ auto vehicleDirectionDiff =
+ calculateAngleDelta(otherVehicleTransform.rotation.yaw, egoVehicleTransform.rotation.yaw);
+
+ bool drivingInSameDirection = true;
+ if (std::abs(vehicleDirectionDiff) > 90.f) {
+ drivingInSameDirection = false;
+ }
+
+ if (drivingInSameDirection) {
+ scene.situationType = ad_rss::situation::SituationType::SameDirection;
+ } else {
+ scene.situationType = ad_rss::situation::SituationType::OppositeDirection;
+ }
+
+ scene.object = otherVehicle;
+ scene.objectRssDynamics = otherDynamics;
+ scene.egoVehicle = egoVehicle;
+ scene.egoVehicleRoad = roadArea;
+ worldModel.scenes.push_back(scene);
+ }
+ }
+ worldModel.egoVehicleRssDynamics = egoDynamics;
+
+ ::ad_rss::situation::SituationSnapshot situationSnapshot;
+ bool result = _rssSituationExtraction->extractSituations(worldModel, situationSnapshot);
+
+ ::ad_rss::state::RssStateSnapshot stateSnapshot;
+ if (result) {
+ result = _rssSituationChecking->checkSituations(situationSnapshot, stateSnapshot);
+ }
+ if (result) {
+ result = _rssResponseResolving->provideProperResponse(stateSnapshot, response);
+ }
+ if (result) {
+ result = ::ad_rss::core::RssResponseTransformation::transformProperResponse(worldModel, response,
+ accelerationRestriction);
+ }
+
+ if (result && visualizeResults) {
+ visualizeRssResults(stateSnapshot, egoVehicleLocation, egoVehicleTransform, world);
+ }
+ return result;
+ }
+ return false;
+ }
+
+ void RssCheck::initVehicle(::ad_rss::world::Object &vehicle) const {
+ vehicle.velocity.speedLon = ::ad_rss::physics::Speed(0.);
+ vehicle.velocity.speedLat = ::ad_rss::physics::Speed(0.);
+ }
+
+ void RssCheck::initEgoVehicleDynamics(::ad_rss::world::RssDynamics &egoDynamics) const {
+ egoDynamics = getEgoVehicleDynamics();
+ }
+
+ void RssCheck::initOtherVehicleDynamics(::ad_rss::world::RssDynamics &dynamics) const {
+ dynamics = getOtherVehicleDynamics();
+ }
+
+ void RssCheck::calculateLatLonVelocities(const cg::Transform &laneLocation,
+ const cg::Vector3D &velocity, ::ad_rss::world::Object &rssObject,
+ bool inverseDirection) const {
+ double roadDirSine = std::sin(laneLocation.rotation.yaw * toRadians);
+ double roadDirCosine = std::cos(laneLocation.rotation.yaw * toRadians);
+ double factor = 1.0;
+ if (inverseDirection) {
+ factor = -1.0;
+ }
+ rssObject.velocity.speedLon =
+ ::ad_rss::physics::Speed(std::abs(roadDirCosine * velocity.x + roadDirSine * velocity.y));
+ rssObject.velocity.speedLat =
+ ::ad_rss::physics::Speed(factor * (-1.0 * roadDirSine * velocity.x + roadDirCosine * velocity.y));
+ }
+
+ void RssCheck::convertAndSetLaneSegmentId(const carla::road::Lane &lane,
+ ::ad_rss::world::LaneSegment &laneSegment) const {
+ uint64_t laneSegmentId = calculateLaneSegmentId(lane);
+ laneSegment.id = laneSegmentId;
+ }
+
+// return front right, back right, back left, front left bounds
+ std::array RssCheck::getVehicleBounds(const cc::Vehicle &vehicle) const {
+ const auto &box = vehicle.GetBoundingBox();
+ const auto &transform = vehicle.GetTransform();
+ const auto location = transform.location + box.location;
+ const auto yaw = transform.rotation.yaw * toRadians;
+ const float cosine = std::cos(yaw);
+ const float sine = std::sin(yaw);
+ cg::Location frontExtent{cosine *box.extent.x, sine *box.extent.x, 0.0f};
+ cg::Location rightExtent{-sine * box.extent.y, cosine *box.extent.y, 0.0f};
+ return {location + frontExtent + rightExtent, location - frontExtent + rightExtent,
+ location - frontExtent - rightExtent, location + frontExtent - rightExtent};
+ }
+
+ uint64_t RssCheck::calculateLaneSegmentId(const carla::road::Lane &lane) const {
+ uint64_t laneSegmentId = lane.GetRoad()->GetId();
+ laneSegmentId = laneSegmentId << 32;
+ uint32_t sectionAndLane = lane.GetLaneSection()->GetId();
+ sectionAndLane <<= 16;
+ int16_t laneId = static_cast(lane.GetId());
+ sectionAndLane |= (0xffff & (laneId + 256));
+ laneSegmentId |= sectionAndLane;
+ return laneSegmentId;
+ }
+
+ void RssCheck::calculateOccupiedRegions(const std::array &bounds,
+ ::ad_rss::world::OccupiedRegionVector &occupiedRegions,
+ const carla::road::Map &carlaRoadMap, const bool drivingInRoadDirection,
+ const cg::Transform &vehicleTransform) const {
+ bool regionValid = true;
+ for (const auto &bound : bounds) {
+ auto boundWaypoint = carlaRoadMap.GetWaypoint(bound);
+
+ if (!boundWaypoint.has_value()) {
+ // vehicle not entirely on road --> stop evaluation
+ regionValid = false;
+ break;
+ }
+ auto &lane = carlaRoadMap.GetLane(*boundWaypoint);
+ auto road = lane.GetRoad();
+ uint64_t laneSegmentId = calculateLaneSegmentId(carlaRoadMap.GetLane(*boundWaypoint));
+
+ auto length = road->GetLength();
+ auto lonPos = boundWaypoint->s;
+ if (!drivingInRoadDirection) {
+ lonPos = length - lonPos;
+ }
+ double lon = std::max(0.0, std::min(lonPos / length, 1.0));
+
+ auto waypointLocation = carlaRoadMap.ComputeTransform(*boundWaypoint);
+ double headingSine = std::sin(waypointLocation.rotation.yaw * toRadians);
+ double headingCosine = std::cos(waypointLocation.rotation.yaw * toRadians);
+
+ auto yawDelta = calculateAngleDelta(waypointLocation.rotation.yaw, vehicleTransform.rotation.yaw);
+ bool inLaneDirection = true;
+ if (std::abs(yawDelta) > 45.f) {
+ inLaneDirection = false;
+ }
+
+ auto width = carlaRoadMap.GetLaneWidth(*boundWaypoint);
+ double latOffset = (headingSine * (waypointLocation.location.x - bound.x) -
+ headingCosine * (waypointLocation.location.y - bound.y));
+ if (!inLaneDirection) {
+ latOffset *= -1.0;
+ }
+
+ double lat = std::max(0.0, std::min(0.5 + latOffset / width, 1.0));
+
+ // find or create occupied region
+ auto regionIt = std::find_if(
+ occupiedRegions.begin(), occupiedRegions.end(),
+ [laneSegmentId](const ::ad_rss::world::OccupiedRegion ®ion) {
+ return region.segmentId == laneSegmentId;
+ });
+ if (regionIt == occupiedRegions.end()) {
+ ::ad_rss::world::OccupiedRegion newRegion;
+ newRegion.segmentId = laneSegmentId;
+ newRegion.lonRange.minimum = ::ad_rss::physics::ParametricValue(lon);
+ newRegion.lonRange.maximum = newRegion.lonRange.minimum;
+ newRegion.latRange.minimum = ::ad_rss::physics::ParametricValue(lat);
+ newRegion.latRange.maximum = newRegion.latRange.minimum;
+ occupiedRegions.push_back(newRegion);
+ } else {
+ ::ad_rss::physics::ParametricValue const lonParam(lon);
+ ::ad_rss::physics::ParametricValue const latParam(lat);
+
+ regionIt->lonRange.minimum = std::min(regionIt->lonRange.minimum, lonParam);
+ regionIt->lonRange.maximum = std::max(regionIt->lonRange.maximum, lonParam);
+ regionIt->latRange.minimum = std::min(regionIt->latRange.minimum, latParam);
+ regionIt->latRange.maximum = std::max(regionIt->latRange.maximum, latParam);
+ }
+ }
+ // expand regions if more than one, ordered from right to left
+ ::ad_rss::physics::ParametricValue lonMinParam(1.0);
+ ::ad_rss::physics::ParametricValue lonMaxParam(0.0);
+
+ for (auto ®ion : occupiedRegions) {
+ lonMinParam = std::min(lonMinParam, region.lonRange.minimum);
+ lonMaxParam = std::max(lonMaxParam, region.lonRange.maximum);
+ if (region != occupiedRegions.front()) {
+ // not the most right one, so extend lat maximum
+ region.latRange.maximum = ::ad_rss::physics::ParametricValue(1.0);
+ }
+ if (region != occupiedRegions.back()) {
+ // not the most left one, so extend lat minimum
+ region.latRange.minimum = ::ad_rss::physics::ParametricValue(0.0);
+ }
+ }
+ for (auto ®ion : occupiedRegions) {
+ region.lonRange.minimum = lonMinParam;
+ region.lonRange.maximum = lonMaxParam;
+ }
+ }
+
+ void RssCheck::visualizeRssResults(::ad_rss::state::RssStateSnapshot stateSnapshot,
+ const cg::Location &egoVehicleLocation,
+ const cg::Transform &egoVehicleTransform,
+ cc::World &world) const {
+ cc::DebugHelper dh = world.MakeDebugHelper();
+ for (std::size_t i = 0; i < stateSnapshot.individualResponses.size(); ++i) {
+ ::ad_rss::state::RssState &state = stateSnapshot.individualResponses[i];
+ carla::rpc::ActorId vehicleId = static_cast(state.objectId);
+ carla::SharedPtr vehicleList =
+ world.GetActors(std::vector{vehicleId});
+ cg::Location egoPoint = egoVehicleLocation;
+ egoPoint.z += 0.05f;
+ const auto yaw = egoVehicleTransform.rotation.yaw;
+ const float cosine = std::cos(yaw * toRadians);
+ const float sine = std::sin(yaw * toRadians);
+ cg::Location lineOffset{-sine * 0.1f, cosine * 0.1f, 0.0f};
+ for (const auto &actor : *vehicleList) {
+ const auto vehicle = boost::dynamic_pointer_cast(actor);
+ cg::Location point = vehicle->GetLocation();
+ point.z += 0.05f;
+ csd::Color lonColor{0u, 255u, 0u};
+ csd::Color latLColor = lonColor;
+ csd::Color latRColor = lonColor;
+ csd::Color indicatorColor = lonColor;
+ bool dangerous = ::ad_rss::state::isDangerous(state);
+ if (dangerous) {
+ indicatorColor = csd::Color{255u, 0u, 0u};
+ }
+ if (!state.longitudinalState.isSafe) {
+ lonColor.r = 255u;
+ if (dangerous) {
+ lonColor.g = 0u;
+ } else {
+ lonColor.g = 255u;
+ }
+ }
+ if (!state.lateralStateLeft.isSafe) {
+ latLColor.r = 255u;
+ if (dangerous) {
+ latLColor.g = 0u;
+ } else {
+ latLColor.g = 255u;
+ }
+ }
+ if (!state.lateralStateRight.isSafe) {
+ latRColor.r = 255u;
+ if (dangerous) {
+ latRColor.g = 0u;
+ } else {
+ latRColor.g = 255u;
+ }
+ }
+ dh.DrawLine(egoPoint, point, 0.1f, lonColor, 0.02f, false);
+ dh.DrawLine(egoPoint - lineOffset, point - lineOffset, 0.1f, latLColor, 0.02f, false);
+ dh.DrawLine(egoPoint + lineOffset, point + lineOffset, 0.1f, latRColor, 0.02f, false);
+ point.z += 3.f;
+ dh.DrawPoint(point, 0.2f, indicatorColor, 0.02f, false);
+ }
+ }
+ }
+
+} // namespace rss
+} // namespace carla
diff --git a/LibCarla/source/carla/rss/RssCheck.h b/LibCarla/source/carla/rss/RssCheck.h
new file mode 100644
index 000000000..046c2e0d7
--- /dev/null
+++ b/LibCarla/source/carla/rss/RssCheck.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2019 Intel Corporation
+//
+// This work is licensed under the terms of the MIT license.
+// For a copy, see .
+
+#pragma once
+
+#include
+#include "carla/client/ActorList.h"
+#include "carla/client/Vehicle.h"
+#include "carla/road/Map.h"
+
+namespace ad_rss {
+namespace state {
+ struct ProperResponse;
+ struct RssStateSnapshot;
+} // namespace state
+namespace core {
+ class RssSituationChecking;
+ class RssSituationExtraction;
+ class RssResponseResolving;
+} // namespace core
+namespace lane {
+ struct VehicleState;
+}
+namespace world {
+ struct AccelerationRestriction;
+ class LaneSegment;
+ class Object;
+ class OccupiedRegion;
+ using OccupiedRegionVector = std::vector<::ad_rss::world::OccupiedRegion>;
+ class RssDynamics;
+ struct Velocity;
+} // namespace world
+} // namespace ad_rss
+
+namespace carla {
+namespace rss {
+
+ namespace cc = carla::client;
+ namespace cg = carla::geom;
+
+ class RssCheck {
+ public:
+
+ RssCheck();
+
+ ~RssCheck();
+
+ bool checkObjects(cc::Timestamp const & timestamp, cc::World &world,
+ carla::SharedPtr const & vehicles,
+ carla::SharedPtr const & carlaEgoVehicle,
+ carla::SharedPtr const & map, ::ad_rss::state::ProperResponse &response,
+ ::ad_rss::world::AccelerationRestriction &restriction,
+ ::ad_rss::world::Velocity &egoVelocity, bool visualizeResults = false);
+
+ const ::ad_rss::world::RssDynamics &getEgoVehicleDynamics() const;
+ void setEgoVehicleDynamics(const ::ad_rss::world::RssDynamics &egoDynamics);
+ const ::ad_rss::world::RssDynamics &getOtherVehicleDynamics() const;
+ void setOtherVehicleDynamics(const ::ad_rss::world::RssDynamics &otherVehicleDynamics);
+
+ private:
+
+ std::shared_ptr<::ad_rss::world::RssDynamics> _egoVehicleDynamics;
+ std::shared_ptr<::ad_rss::world::RssDynamics> _otherVehicleDynamics;
+
+ std::shared_ptr<::ad_rss::core::RssSituationExtraction> _rssSituationExtraction;
+ std::shared_ptr<::ad_rss::core::RssSituationChecking> _rssSituationChecking;
+ std::shared_ptr<::ad_rss::core::RssResponseResolving> _rssResponseResolving;
+
+ uint64_t calculateLaneSegmentId(const carla::road::Lane &lane) const;
+
+ void calculateLatLonVelocities(const cg::Transform &laneLocation,
+ const cg::Vector3D &velocity,
+ ::ad_rss::world::Object &rssObject, bool inverseDirection) const;
+
+ void calculateOccupiedRegions(const std::array &bounds,
+ ::ad_rss::world::OccupiedRegionVector &occupiedRegions, const carla::road::Map &map,
+ const bool drivingInRoadDirection,
+ const cg::Transform &vehicleTransform) const;
+
+ void convertAndSetLaneSegmentId(const carla::road::Lane &lane,
+ ::ad_rss::world::LaneSegment &laneSegment) const;
+
+ std::array getVehicleBounds(const cc::Vehicle &vehicle) const;
+
+ void initVehicle(::ad_rss::world::Object &vehicle) const;
+ void initEgoVehicleDynamics(::ad_rss::world::RssDynamics &dynamics) const;
+ void initOtherVehicleDynamics(::ad_rss::world::RssDynamics &dynamics) const;
+
+ void visualizeRssResults(::ad_rss::state::RssStateSnapshot stateSnapshot,
+ const cg::Location &egoVehicleLocation,
+ const cg::Transform &egoVehicleTransform, cc::World &world) const;
+ };
+
+} // namespace rss
+} // namespace carla
diff --git a/LibCarla/source/carla/rss/RssRestrictor.cpp b/LibCarla/source/carla/rss/RssRestrictor.cpp
new file mode 100644
index 000000000..bf7b8a072
--- /dev/null
+++ b/LibCarla/source/carla/rss/RssRestrictor.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2019 Intel Corporation
+//
+// This work is licensed under the terms of the MIT license.
+// For a copy, see .
+
+#include "carla/rss/RssRestrictor.h"
+#include "carla/rpc/VehicleControl.h"
+#include "carla/rpc/VehiclePhysicsControl.h"
+
+#include "ad_rss/world/AccelerationRestriction.hpp"
+#include "ad_rss/world/Velocity.hpp"
+
+namespace carla {
+namespace rss {
+
+ RssRestrictor::RssRestrictor() {}
+
+ RssRestrictor::~RssRestrictor() = default;
+
+ carla::rpc::VehicleControl RssRestrictor::restrictVehicleControl(
+ const carla::rpc::VehicleControl &vehicleControl, const ad_rss::world::AccelerationRestriction &restriction,
+ const ad_rss::world::Velocity &egoVelocity, const carla::rpc::VehiclePhysicsControl &vehiclePhysics) {
+ carla::rpc::VehicleControl restrictedVehicleControl(vehicleControl);
+
+ // Pretty basic implementation of a RSS restrictor modifying the
+ // VehicleControl according to the given
+ // restrictions. Basic braking and countersteering actions are applied.
+
+ ad_rss::physics::Acceleration zeroAccel(0.0);
+
+ float mass = vehiclePhysics.mass;
+ float sumMaxBrakeTorque = 0.f;
+ float radius = 1.f;
+ for (auto const &wheel : vehiclePhysics.wheels) {
+ sumMaxBrakeTorque += wheel.max_brake_torque;
+ radius = wheel.radius;
+ }
+
+ // do not apply any restrictions when in reverse gear
+ if (!vehicleControl.reverse) {
+
+ // decelerate
+ if (restriction.longitudinalRange.maximum < zeroAccel) {
+ restrictedVehicleControl.throttle = 0.0f;
+
+ double sumBrakeTorque = mass * std::fabs(static_cast(restriction.longitudinalRange.minimum)) *
+ radius / 100.0;
+ restrictedVehicleControl.brake =
+ std::min(static_cast(sumBrakeTorque / sumMaxBrakeTorque), 1.0f);
+ }
+
+ if (restriction.lateralLeftRange.maximum <= ad_rss::physics::Acceleration(0.0)) {
+ if (egoVelocity.speedLat < ad_rss::physics::Speed(0.0)) {
+ // driving to the left
+ if (egoVelocity.speedLon != ad_rss::physics::Speed(0.0)) {
+ double angle = std::atan(egoVelocity.speedLat / egoVelocity.speedLon);
+ restrictedVehicleControl.steer = -1.f * static_cast(angle);
+ }
+ }
+ }
+ if (restriction.lateralRightRange.maximum <= ad_rss::physics::Acceleration(0.0)) {
+ if (egoVelocity.speedLat > ad_rss::physics::Speed(0.0)) {
+ // driving to the right
+ if (egoVelocity.speedLon != ad_rss::physics::Speed(0.0)) {
+ double angle = std::atan(egoVelocity.speedLat / egoVelocity.speedLon);
+ restrictedVehicleControl.steer = -1.f * static_cast(angle);
+ }
+ }
+ }
+ }
+ return restrictedVehicleControl;
+ }
+
+} // namespace rss
+} // namespace carla
diff --git a/LibCarla/source/carla/rss/RssRestrictor.h b/LibCarla/source/carla/rss/RssRestrictor.h
new file mode 100644
index 000000000..c7ed09a76
--- /dev/null
+++ b/LibCarla/source/carla/rss/RssRestrictor.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Intel Corporation
+//
+// This work is licensed under the terms of the MIT license.
+// For a copy, see .
+
+#pragma once
+
+#include
+
+namespace ad_rss {
+namespace world {
+ struct AccelerationRestriction;
+ struct Velocity;
+} // namespace world
+} // namespace ad_rss
+
+namespace carla {
+namespace rpc {
+ class VehicleControl;
+ class VehiclePhysicsControl;
+} // namespace rpc
+
+namespace rss {
+
+ class RssRestrictor {
+ public:
+
+ RssRestrictor();
+
+ ~RssRestrictor();
+
+ carla::rpc::VehicleControl restrictVehicleControl(const carla::rpc::VehicleControl &vehicleControl,
+ const ad_rss::world::AccelerationRestriction &restriction,
+ const ad_rss::world::Velocity &egoVelocity,
+ const carla::rpc::VehiclePhysicsControl &vehiclePhysics);
+
+ private:
+
+ };
+
+} // namespace rss
+} // namespace carla
diff --git a/LibCarla/source/carla/rss/RssSensor.cpp b/LibCarla/source/carla/rss/RssSensor.cpp
new file mode 100644
index 000000000..ae2cfb2ad
--- /dev/null
+++ b/LibCarla/source/carla/rss/RssSensor.cpp
@@ -0,0 +1,146 @@
+// Copyright (c) 2019 Intel Corporation
+//
+// This work is licensed under the terms of the MIT license.
+// For a copy, see .
+
+#include "carla/rss/RssSensor.h"
+
+#include "carla/Logging.h"
+#include "carla/client/detail/Simulator.h"
+
+#include "carla/client/ActorList.h"
+#include "carla/rss/RssCheck.h"
+#include "carla/sensor/data/RssResponse.h"
+
+#include
+#include
+#include
+
+#include
+
+namespace carla {
+namespace client {
+
+ namespace cc = carla::client;
+ namespace csd = carla::sensor::data;
+
+ RssSensor::~RssSensor() = default;
+
+ void RssSensor::Listen(CallbackFunctionType callback) {
+ if (_is_listening) {
+ log_error(GetDisplayId(), ": already listening");
+ return;
+ }
+
+ if (GetParent() == nullptr) {
+ throw_exception(std::runtime_error(GetDisplayId() + ": not attached to vehicle"));
+ return;
+ }
+
+ _map = GetWorld().GetMap();
+ DEBUG_ASSERT(_map != nullptr);
+
+ mRssCheck = std::make_shared<::carla::rss::RssCheck>();
+
+ auto self = boost::static_pointer_cast(shared_from_this());
+
+ log_debug(GetDisplayId(), ": subscribing to tick event");
+ GetEpisode().Lock()->RegisterOnTickEvent(
+ [cb = std::move(callback), weak_self = WeakPtr(self)](const auto &snapshot) {
+ auto self = weak_self.lock();
+ if (self != nullptr) {
+ auto data = self->TickRssSensor(snapshot.GetTimestamp());
+ if (data != nullptr) {
+ cb(std::move(data));
+ }
+ }
+ });
+
+ _is_listening = true;
+ }
+
+ SharedPtr RssSensor::TickRssSensor(const Timestamp ×tamp) {
+ try {
+ bool result = false;
+ ::ad_rss::state::ProperResponse response;
+ ::ad_rss::world::AccelerationRestriction accelerationRestriction;
+ ::ad_rss::world::Velocity egoVelocity;
+ if (_processing_lock.try_lock()) {
+ cc::World world = GetWorld();
+ SharedPtr actors = world.GetActors();
+ SharedPtr vehicles = actors->Filter("vehicle.*");
+
+ // check all object<->ego pairs with RSS and calculate proper response
+ result =
+ mRssCheck->checkObjects(timestamp, world, vehicles, GetParent(), _map, response,
+ accelerationRestriction,
+ egoVelocity, visualize_results);
+ _processing_lock.unlock();
+
+ csd::LongitudinalResponse longitudinalResponse;
+ csd::LateralResponse lateralResponseRight;
+ csd::LateralResponse lateralResponseLeft;
+
+ switch (response.longitudinalResponse) {
+ case ::ad_rss::state::LongitudinalResponse::None:
+ longitudinalResponse = csd::LongitudinalResponse::None;
+ break;
+ case ::ad_rss::state::LongitudinalResponse::BrakeMinCorrect:
+ longitudinalResponse = csd::LongitudinalResponse::BrakeMinCorrect;
+ break;
+ case ::ad_rss::state::LongitudinalResponse::BrakeMin:
+ longitudinalResponse = csd::LongitudinalResponse::BrakeMin;
+ break;
+ }
+ switch (response.lateralResponseRight) {
+ case ::ad_rss::state::LateralResponse::None:
+ lateralResponseRight = csd::LateralResponse::None;
+ break;
+ case ::ad_rss::state::LateralResponse::BrakeMin:
+ lateralResponseRight = csd::LateralResponse::BrakeMin;
+ break;
+ }
+ switch (response.lateralResponseLeft) {
+ case ::ad_rss::state::LateralResponse::None:
+ lateralResponseLeft = csd::LateralResponse::None;
+ break;
+ case ::ad_rss::state::LateralResponse::BrakeMin:
+ lateralResponseLeft = csd::LateralResponse::BrakeMin;
+ break;
+ }
+ return MakeShared(timestamp.frame, timestamp.elapsed_seconds,
+ GetTransform(),
+ result, longitudinalResponse, lateralResponseRight,
+ lateralResponseLeft, accelerationRestriction, egoVelocity);
+ } else {
+ return nullptr;
+ }
+ } catch (const std::exception &e) {
+ std::cout << e.what() << std::endl;
+ _processing_lock.unlock();
+ return nullptr;
+ }
+ }
+
+ void RssSensor::Stop() {
+ _is_listening = false;
+ }
+
+ const ::ad_rss::world::RssDynamics &RssSensor::GetEgoVehicleDynamics() const {
+ return mRssCheck->getEgoVehicleDynamics();
+ }
+
+ void RssSensor::SetEgoVehicleDynamics(const ::ad_rss::world::RssDynamics &dynamics) {
+ mRssCheck->setEgoVehicleDynamics(dynamics);
+ }
+
+ const ::ad_rss::world::RssDynamics &RssSensor::GetOtherVehicleDynamics() const {
+ return mRssCheck->getOtherVehicleDynamics();
+ }
+
+ void RssSensor::SetOtherVehicleDynamics(const ::ad_rss::world::RssDynamics &dynamics) {
+ mRssCheck->setOtherVehicleDynamics(dynamics);
+ }
+
+} // namespace client
+} // namespace carla
diff --git a/LibCarla/source/carla/rss/RssSensor.h b/LibCarla/source/carla/rss/RssSensor.h
new file mode 100644
index 000000000..a51746140
--- /dev/null
+++ b/LibCarla/source/carla/rss/RssSensor.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2019 Intel Corporation
+//
+// This work is licensed under the terms of the MIT license.
+// For a copy, see .
+
+#pragma once
+
+#include
+#include "carla/client/Sensor.h"
+
+namespace ad_rss {
+namespace world {
+ class RssDynamics;
+} // namespace world
+} // namespace ad_rss
+
+namespace carla {
+namespace rss {
+ class RssCheck;
+} // namespace rss
+namespace client {
+
+ class RssSensor final : public Sensor {
+ public:
+
+ using Sensor::Sensor;
+
+ ~RssSensor();
+
+ /// Register a @a callback to be executed each time a new measurement is
+ /// received.
+ ///
+ /// @warning Calling this function on a sensor that is already listening
+ /// steals the data stream from the previously set callback. Note that
+ /// several instances of Sensor (even in different processes) may point to
+ /// the same sensor in the simulator.
+ void Listen(CallbackFunctionType callback) override;
+
+ /// Stop listening for new measurements.
+ void Stop() override;
+
+ /// Return whether this Sensor instance is currently listening to the
+ /// associated sensor in the simulator.
+ bool IsListening() const override { return _is_listening;
+ }
+
+ bool visualize_results = false;
+
+ const ::ad_rss::world::RssDynamics &GetEgoVehicleDynamics() const;
+ void SetEgoVehicleDynamics(const ::ad_rss::world::RssDynamics &egoDynamics);
+ const ::ad_rss::world::RssDynamics &GetOtherVehicleDynamics() const;
+ void SetOtherVehicleDynamics(const ::ad_rss::world::RssDynamics &otherVehicleDynamics);
+
+ private:
+
+ SharedPtr TickRssSensor(const Timestamp ×tamp);
+
+ std::mutex _processing_lock;
+
+ bool _is_listening = false;
+
+ SharedPtr