Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
* Added `Map.geolocation_to_transform` Python API that maps a `GeoLocation` back to a world-space `Location`, the inverse of `Map.transform_to_geolocation`.
* Reworked the sensor render pipeline and quality tiers by pooling GPU readbacks, gating GBuffer capture on listeners, adding a per-camera ray-tracing toggle, and introducing four server launch tiers (Low, Medium, High, Epic) selectable via `-quality-level=<Tier>` (case-sensitive, Epic by default). Each tier applies a coherent CVar configuration at engine init that persists across runs without manual `GameUserSettings.ini` cleanup.
* Added weather recording and replay, simultaneous record-and-replay, `stop_replayer` flag on `start_recorder`, `map_override` and follow-offset arguments on `replay_file`, and traffic-sign follow targets in the replayer (ported from ue4-dev)
* Reworked the ROS 2 native sensor publishers behind a shared publisher and subscriber template layer, unified the camera and point-cloud publishers, and added an Ackermann control subscriber so vehicles can be driven from ROS 2 Ackermann messages.
* Added `carla.Velocity`, `carla.AngularVelocity`, `carla.Acceleration`, `carla.Quaternion`, and right-handed vector conversions to the geometry types, and corrected the pitch and roll rotation order. Clients that previously compensated for the incorrect rotation will need to remove that workaround.
* Added a `ROS2TopicVisibility` startup flag that sets whether sensor topics are exposed by default when the server launches.
* Added the V2X sensor family: a CAM service sensor (`sensor.other.v2x`), a custom binary-payload sensor (`sensor.other.v2x_custom`) with channel selection and multiple messages per frame, a configurable path-loss propagation model, and owner-less infrastructure (V2I) sensors.
* Fixed the IMU sensor compass yaw orientation and corrected the order in which sensors are disposed when their parent actor is destroyed.
* Hardened pedestrian navigation against a null dereference when collecting the traffic lights used for walker routing.
* Hardened UObject ownership in the Carla plugin by migrating UPROPERTY raw pointers to `TObjectPtr<>`, adding mesh caches, enabling async heightmap streaming, and converting catalog assets to soft references.
* Corrected the Semantic Segmentation camera class table in `Docs/ref_sensors.md` to match the actual 29-class taxonomy defined in `ObjectLabel.h` and `CityScapesPalette.h`. The previous table reflected the legacy 0.8.x CityScapes taxonomy (22 classes), which caused mismatches between documentation and engine output. This update aligns the documentation with the true engine enum values and RGB palette, preventing ground-truth mapping errors when building perception pipelines.
* Fixed several legacy UE4-era bugs across LibCarla and the Carla plugin affecting lidar memory reset, DVS validation, camera profiling, image reads, sensor materials, and Python sensor teardown.
Expand Down
8 changes: 7 additions & 1 deletion LibCarla/source/carla/client/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ namespace client {
if (tics_correct >= 2)
return id;

Tick(local_timeout);
if (settings.synchronous_mode) {
// only drive the simulation forward in synchronous mode; in
// asynchronous mode wait for the server to advance on its own
Tick(local_timeout);
} else {
WaitForTick(local_timeout);
}
}

log_warning("World::ApplySettings: After", number_of_attemps, " attemps, the settings were not correctly set. Please check that everything is consistent.");
Expand Down
27 changes: 14 additions & 13 deletions LibCarla/source/carla/nav/WalkerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
#include "carla/nav/WalkerManager.h"

#include "carla/Logging.h"
#include "carla/client/ActorSnapshot.h"
#include "carla/client/ActorList.h"
#include "carla/client/Waypoint.h"
#include "carla/client/World.h"
#include "carla/client/detail/Simulator.h"
#include "carla/nav/Navigation.h"
#include "carla/rpc/Actor.h"

namespace carla {
namespace nav {
Expand Down Expand Up @@ -282,18 +281,20 @@ namespace nav {
carla::client::World world = _simulator.lock()->GetWorld();

_traffic_lights.clear();
std::vector<carla::rpc::Actor> actors = _simulator.lock()->GetAllTheActorsInTheEpisode();
for (auto actor : actors) {
carla::client::ActorSnapshot snapshot = _simulator.lock()->GetActorSnapshot(actor.id);
auto actors = world.GetActors();
for (auto const &actor : *actors) {
// check traffic lights only
if (actor.description.id == "traffic.traffic_light") {
// get the TL actor
SharedPtr<carla::client::TrafficLight> tl =
std::static_pointer_cast<carla::client::TrafficLight>(world.GetActor(actor.id));
// get the waypoints where the TL affects
std::vector<SharedPtr<carla::client::Waypoint>> list = tl->GetStopWaypoints();
for (auto &way : list) {
_traffic_lights.emplace_back(tl, way->GetTransform().location);
if (actor->GetTypeId() == "traffic.traffic_light") {
// skip anything that reports the traffic-light type but does not
// resolve to a TrafficLight actor, otherwise the dereference below
// would crash on a null pointer
auto tl = std::dynamic_pointer_cast<carla::client::TrafficLight>(actor);
if (tl != nullptr) {
// get the waypoints where the TL affects
std::vector<SharedPtr<carla::client::Waypoint>> list = tl->GetStopWaypoints();
for (auto const &way : list) {
_traffic_lights.emplace_back(tl, way->GetTransform().location);
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions PythonAPI/examples/ros2/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ARG ROS_DISTRO=humble
FROM osrf/ros:${ROS_DISTRO}-desktop

# FastDDS (rmw_fastrtps_cpp) ships in the base image; select it explicitly.
ENV RMW_IMPLEMENTATION=rmw_fastrtps_cpp
64 changes: 57 additions & 7 deletions PythonAPI/examples/ros2/run_rviz.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,74 @@
#!/bin/bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

function transfer_x11_permissions() {
# store X11 access rights in temp file to be passed into docker container
XAUTH=/tmp/.docker.xauth
touch $XAUTH
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
# --- Defaults ---
DISTRO="humble"

# --- Argument parsing ---
usage() {
cat <<EOF
Usage: $0 [--distro=<distro>]

Options:
--distro ROS 2 distribution to use. Supported: humble, jazzy (default: humble)

Examples:
$0 --distro=humble
$0 --distro=jazzy
EOF
exit 1
}

transfer_x11_permissions
for arg in "$@"; do
case "$arg" in
--distro=*) DISTRO="${arg#*=}" ;;
--help|-h) usage ;;
*) echo "Unknown argument: $arg"; usage ;;
esac
done

# --- Validate ---
case "$DISTRO" in
humble|jazzy) ;;
*) echo "Unsupported distro '${DISTRO}'. Supported values: humble, jazzy"; exit 1 ;;
esac

IMAGE_NAME="carla-rviz-${DISTRO}-fastdds"

# --- Build ---
function build_image() {
echo "[RViz] Building Docker image '${IMAGE_NAME}' (distro=${DISTRO})..."
docker build \
--build-arg ROS_DISTRO="${DISTRO}" \
--file "${SCRIPT_DIR}/Dockerfile" \
--tag "${IMAGE_NAME}" \
"${SCRIPT_DIR}"
}

if ! docker image inspect "${IMAGE_NAME}" &>/dev/null; then
build_image
fi

# --- X11 permissions ---
XAUTH=/tmp/.docker.xauth
touch "$XAUTH"
xauth nlist "$DISPLAY" | sed -e 's/^..../ffff/' | xauth -f "$XAUTH" nmerge -

# --- Run ---
echo "[RViz] Launching RViz2 (distro=${DISTRO})..."
docker run \
--rm \
--net=host \
--env="DISPLAY=$DISPLAY" \
--env="XAUTHORITY=$XAUTH" \
--env="RMW_IMPLEMENTATION=rmw_fastrtps_cpp" \
--env="FASTRTPS_DEFAULT_PROFILES_FILE=/config/fastrtps-profile.xml" \
--volume="${SCRIPT_DIR}/config:/config:ro" \
--volume="${SCRIPT_DIR}/rviz:/rviz:rw" \
--volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
--volume="$XAUTH:$XAUTH" \
osrf/ros:humble-desktop \
"${IMAGE_NAME}" \
ros2 run rviz2 rviz2 -d /rviz/ros2_native.rviz
4 changes: 3 additions & 1 deletion PythonAPI/test/smoke/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def setUp(self):

def tearDown(self):
self.world.apply_settings(self.settings)
self.world.tick()
if self.settings.synchronous_mode:
# only tick when the restored settings keep synchronous mode active
self.world.tick()
self.settings = None
super(SyncSmokeTest, self).tearDown()
124 changes: 124 additions & 0 deletions PythonAPI/test/smoke/test_walker_navigation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma de
# Barcelona (UAB).
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.

import time

import carla

from . import SyncSmokeTest


WORLD = "Town10HD_Opt"


class TestWalkerNavigation(SyncSmokeTest):
"""Coverage for the WalkerManager traffic-light gather and AI walker routing.

Driving a ``controller.ai.walker`` makes the server build a pedestrian
route, which runs ``WalkerManager::GetAllTrafficLightWaypoints`` to collect
every ``traffic.traffic_light`` actor affecting the path. That gather was
reworked to iterate ``World::GetActors`` and ``dynamic_pointer_cast`` each
candidate to ``TrafficLight`` behind a null guard, replacing an unchecked
``static_pointer_cast`` plus a dead per-actor snapshot query.

With the maps shipped in the packaged build every ``traffic.traffic_light``
actor resolves to a real ``TrafficLight``, so this test does not drive the
null-guard branch itself; that branch is defensive and not reproducible from
the client. What it does cover is that the reworked gather and the AI walker
routing path still run end to end without taking the server down, and that a
started controller actually moves the walker. No prior smoke test exercised
the AI controller path at all.
"""

def setUp(self):
super(TestWalkerNavigation, self).setUp()
if self.world.get_map().name.split("/")[-1] != WORLD:
self.client.load_world(WORLD)
time.sleep(5)
self.world = self.client.get_world()
settings = carla.WorldSettings(
no_rendering_mode=False,
synchronous_mode=True,
fixed_delta_seconds=0.05)
self.world.apply_settings(settings)
self.world.tick()

def tearDown(self):
# SmokeTest.tearDown() loads Town03, which is not shipped in the
# packaged build. Reload Town10HD_Opt instead so the next test starts
# from a known good state.
self.world.apply_settings(self.settings)
if self.settings.synchronous_mode:
self.world.tick()
self.settings = None
self.client.load_world(WORLD)
time.sleep(5)
self.world = None
self.client = None

def _random_navigation_location(self):
location = self.world.get_random_location_from_navigation()
self.assertIsNotNone(
location,
"Navigation mesh is unavailable; cannot exercise walker routing")
return location

def _destination_away_from(self, origin, min_distance=10.0):
# Pick a navigation point a few metres away so the route is non-trivial
# and the walker has somewhere to actually move towards.
for _ in range(20):
candidate = self._random_navigation_location()
if candidate.distance(origin) >= min_distance:
return candidate
return candidate

def test_walker_ai_controller_routes_without_crash(self):
print("TestWalkerNavigation.test_walker_ai_controller_routes_without_crash")

blueprint_library = self.world.get_blueprint_library()
walker_bp = blueprint_library.filter("walker.pedestrian.*")[0]
if walker_bp.has_attribute("is_invincible"):
walker_bp.set_attribute("is_invincible", "false")
controller_bp = blueprint_library.find("controller.ai.walker")

spawn_location = self._random_navigation_location()
spawn_location.z += 1.0
walker = self.world.spawn_actor(walker_bp, carla.Transform(spawn_location))
self.assertIsNotNone(walker, "Failed to spawn a walker actor")

controller = None
try:
controller = self.world.spawn_actor(
controller_bp, carla.Transform(), attach_to=walker)
self.world.tick()

controller.start()
controller.go_to_location(self._destination_away_from(spawn_location))
controller.set_max_speed(1.4)

start = walker.get_location()
for _ in range(120):
self.world.tick()

# Primary guarantee: the route computation (which runs the
# traffic-light gather) did not crash the server, so it still
# answers requests.
snapshot = self.world.get_snapshot()
self.assertIsNotNone(snapshot)
self.assertTrue(walker.is_alive, "Walker died during navigation")

# Secondary signal: a started AI controller should have moved the
# walker, confirming a route was actually built and followed.
travelled = walker.get_location().distance(start)
self.assertGreater(
travelled, 0.3,
"Walker did not move under AI control; routing likely failed")
finally:
if controller is not None:
controller.stop()
controller.destroy()
walker.destroy()
self.world.tick()
2 changes: 1 addition & 1 deletion PythonAPI/test/smoke_test_list.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
smoke.test_client smoke.test_sync smoke.test_sensor_determinism smoke.test_collision_determinism smoke.test_vehicle_physics smoke.test_vehicle_telemetry smoke.test_props_loading smoke.test_sensor_tick_time smoke.test_map smoke.test_snapshot smoke.test_lidar smoke.test_streamming smoke.test_spawnpoints smoke.test_blueprint smoke.test_collision_sensor smoke.test_world smoke.test_determinism smoke.test_actor_introspection smoke.test_debug_clear smoke.test_geoconversion smoke.test_walker_bounding_box smoke.test_recorder smoke.test_replay_no_actor_aliasing smoke.test_traffic_manager_sync_step smoke.test_traffic_manager_large_vehicle smoke.test_v2x
smoke.test_client smoke.test_sync smoke.test_sensor_determinism smoke.test_collision_determinism smoke.test_vehicle_physics smoke.test_vehicle_telemetry smoke.test_props_loading smoke.test_sensor_tick_time smoke.test_map smoke.test_snapshot smoke.test_lidar smoke.test_streamming smoke.test_spawnpoints smoke.test_blueprint smoke.test_collision_sensor smoke.test_world smoke.test_determinism smoke.test_actor_introspection smoke.test_debug_clear smoke.test_geoconversion smoke.test_walker_bounding_box smoke.test_recorder smoke.test_replay_no_actor_aliasing smoke.test_traffic_manager_sync_step smoke.test_traffic_manager_large_vehicle smoke.test_v2x smoke.test_walker_navigation
Loading