CARLA world - SethKitchen/ThreeMojo GitHub Wiki
CARLA world
extensions/carla/world.mojo holds a CARLA world: a map, the actors on it and their physics, stepped in fixed ticks. The vehicles and walkers move by CARLA physics. The traffic lights and signs come from the map's signals and act on the vehicles in their boxes.
The port follows the CARLA source at commit 1360bb9: LibCarla/source/carla/client and rpc for the API and the records. The traffic lights and signs, the blueprint definitions and the weather ranges come from CARLA's simulator plugin, in Carla/Traffic, Carla/Actor, Carla/Game and Carla/Weather. See CARLA maps for the map and CARLA geometry for the frame.
Modules
| Module | What it gives |
|---|---|
world |
World, EpisodeSettings and LabelledPoint. |
world_snapshot |
WorldSnapshot, ActorSnapshot, Timestamp and TrafficLightData. |
actor |
ActorId, ActorKind, ActorState, AttachmentType, TrafficLightState and the Actor record. |
vehicle |
VehicleLightState, VehicleDoor, VehicleWheelLocation, VehicleData and the vehicle models. |
walker |
WalkerBoneControlIn, WalkerBoneControlOut and the walker record. |
traffic_light |
TrafficLightController, TrafficLightGroup, TrafficLight and TrafficLightManager. |
traffic_sign |
TrafficSign, TriggerBox and the box builders. |
blueprint |
ActorAttribute, ActorBlueprint, BlueprintLibrary and the catalog. |
weather |
WeatherParameters and CARLA's 23 presets. |
Build a world and step it
A world takes a map. Its road and sidewalk surfaces become static colliders. The ticks need a fixed step.
from extensions.carla.opendrive import load_opendrive_file
from extensions.carla.world import EpisodeSettings, World
from units.si import Duration, SECOND
var world = World(load_opendrive_file("assets/carla/town.xodr"))
var settings = EpisodeSettings()
settings.synchronous_mode = True
settings.fixed_delta_seconds = Duration(0.05, SECOND)
_ = world.apply_settings(settings)
var frame = world.tick()
var snapshot = world.get_snapshot()
The world is an entity registry. Each actor has an ActorId, from 1 up. The spectator is actor 1, and the traffic lights and signs follow it. An operation on an actor is a method of the world that takes the id.
One tick
tick is a fixed-step game loop. It does these steps in this order:
- The frame counts up, and the time moves on by
fixed_delta_seconds. - The give-way timers of the stop and yield signs run down.
- The traffic-light groups advance their cycles.
- The physics steps. The tick is cut into the fewest substeps no longer than
max_substep_delta_time, and intomax_substepsat most. - The world finds the road boxes that each vehicle is in. It tells the lights and signs about each vehicle that came or went.
- The world takes its snapshot.
Timestamp quantities
Timestamp takes Duration64 for elapsed, delta and platform time. Its
elapsed, delta and platform accessors return the stored Float64 value
without a Float32 conversion. Timestamp.from_seconds is the named adapter
for CARLA's raw Float64 second fields. The fields and printed record format
remain unchanged.
A frame count must be nonnegative. Elapsed and delta time must be finite and
nonnegative. Platform time must be finite and can be signed. Each accessor
and WorldSnapshot construction checks the mutable timestamp before use.
A zero delta is valid. Negative zero keeps its original bits.
This precision contract covers the timestamp boundary. Episode settings and
the physical integration step still use their existing Float32 duration.
The settings reject nonfinite times. The substep calculation clamps its
Float64 count before conversion to Int. tick checks timing settings,
bounded substep count and clock advancement before it changes world state.
Signal ids in snapshots
A snapshot holds at most 32 UTF-8 bytes of each traffic light or sign id. This is a byte limit, not a character count. If the 32-byte limit would split a codepoint, the snapshot ends before that codepoint. The result stays valid UTF-8. An ASCII id keeps its first 32 characters.
The world keeps the full OpenDRIVE id. get_opendrive_id and get_traffic_light_from_opendrive use that full id. Only the snapshot field is shortened. Recording and replay can tick a world with these ids; their actor records use actor ids and positions rather than snapshot sign strings.
Spawn and control actors
spawn_actor takes a blueprint from the world's library and a transform. A vehicle.* blueprint makes a vehicle and a walker.* blueprint makes a walker. A sensor.*, static.*, util.* or controller.* blueprint makes an actor with no body.
from extensions.carla.physics.vehicle_control import VehicleControl
from math.vector3 import Vector3
var blueprint = world.get_blueprint_library().at("vehicle.lincoln.mkz")
var car = world.spawn_actor(blueprint, world.get_spawn_points()[0])
var camera_bp = world.get_blueprint_library().at("sensor.camera.rgb")
var mount = world.get_transform(car)
mount.location = Vector3(1.5, 0, 2.4)
var camera = world.spawn_actor(camera_bp, mount, car)
var control = VehicleControl()
control.throttle = 0.5
world.apply_control(car, control)
A spawn fails when the new vehicle's or walker's box meets the box of another vehicle or walker. try_spawn_actor returns None instead. A failed spawn leaves the actor and physics lists unchanged and does not consume an actor id. Required vehicle attributes are checked before the world adds a body. A sensor or a prop can have a parent, and then its transform is in the parent's frame.
The spawn points are the start of each lane of the map's topology, 0.5 m above the road. CARLA makes them so for a map that has none placed.
What an actor gives
| Question | Method |
|---|---|
| Where is it? | get_transform, get_location, get_bounding_box |
| How does it move? | get_velocity, get_angular_velocity, get_acceleration |
| Move it | set_transform, set_location, set_target_velocity, set_target_angular_velocity |
| Push it | add_impulse, add_force, add_torque, add_angular_impulse, set_enable_gravity |
| Drive a vehicle | apply_control, apply_ackermann_control, apply_physics_control, enable_constant_velocity |
| Read a vehicle | get_control, get_telemetry_data, get_physics_control, get_failure_state, get_wheel_steer_angle |
| A vehicle's lights and doors | set_light_state, get_light_state, open_door, close_door, is_door_open |
| A vehicle's signals | get_speed_limit, get_traffic_light_state, is_at_traffic_light, get_traffic_light |
| Move a walker | apply_walker_control, get_walker_control |
| A walker's bones | set_bones_transform, get_bones_transform, blend_pose |
An actor also carries the data that the renderer and the sensors read. The data is its blueprint id, its attributes, such as color, its bounding box and its semantic tags.
The velocity is in m/s. The angular velocity is in degrees per second, as CARLA reports it. The acceleration is the change of the velocity since the last tick, over the tick.
Traffic lights
A light belongs to a controller, and a controller belongs to a group, one group for each junction. The world places each traffic-light signal that a controller holds. It also places each traffic-light signal outside a junction that no controller holds. That light gets its own group and controller, with a red stage of 10 s.
A controller runs its stages in turn: 10 s green, 3 s yellow and 2 s red. A group runs its controllers in turn. While one controller runs its cycle, the lights of the others stay red.
A stage changes on the first tick that takes the elapsed time past the stage's time. The elapsed time then starts again from zero. With 0.25 s ticks, green changes to yellow on tick 41, because 40 ticks give exactly 10 s.
| Question | Method |
|---|---|
| The state | get_traffic_light_state_of, set_traffic_light_state |
| The times | get_light_time, set_light_time, get_elapsed_time |
| Stop the clock | freeze, freeze_all_traffic_lights, is_frozen |
| Start again | reset_group, reset_all_traffic_lights |
| The group | get_group_traffic_lights, get_traffic_lights_in_junction |
| The lanes | get_affected_lane_waypoints, get_stop_waypoints, get_trigger_volume |
set_traffic_light_state sets one light. The controller does not know, and its next stage sets the light again. freeze stops every light, as in CARLA.
get_traffic_lights_from_waypoint finds the lights within a distance ahead of a waypoint. The distance is a Length64, so a long search keeps its Float64 meters. It must be finite and nonnegative.
A light's boxes
Each lane that a light holds gets a box 3 m before the light, against the lane's traffic. The box is 3 m long, half a lane wide and 2 m high. On a junction lane with one predecessor outside the junction, the box moves to that predecessor.
A vehicle that enters a box takes the light's state, and each change of the light reaches it at once. A vehicle is told green again after leaving the last occupied box of that light. The light's internal vehicle list has one entry per occupied box, so duplicate vehicle ids there are intentional.
Trigger offset direction
The box center moves against the resolved lane's travel direction. Right-hand negative lanes and left-hand positive lanes travel with increasing s. Their offsets subtract from s. The other lanes add to s.
A light, stop or yield box uses a 3 m offset. A speed-limit box uses its half size. Each center stays inside its lane section, at least 0.00001 m from either end. A section shorter than 0.00002 m uses its midpoint instead. An offset does not cross a section boundary.
The direction belongs to the waypoint that receives the box. A junction reference can move the box to a predecessor with a different lane id or road rule.
At s = 50 on left-hand lane 1, the light box center is s = 47. The old lane-sign rule put it at s = 53. Left-hand scenario replay can therefore enter the box earlier. Existing right-hand results stay the same when a predecessor does not change the lane sign.
Traffic signs
The world places a stop sign for each signal of type 206, except the painted "Stencil_STOP". It places a yield sign for type 205. It places a speed-limit sign for type 274 with a subtype from 30 to 120 in steps of ten.
- A speed-limit box gives the vehicle the sign's limit. The limit stays after the vehicle leaves. Before any box, the limit is 30 km/h.
- A stop sign's effect box tells a vehicle red. After 2 s, the sign lets the vehicle go if its check boxes are empty. Otherwise it checks again after 1 s.
- A yield sign checks at once, and again after 0.5 s.
- A vehicle that leaves a check box starts a check after 0.5 s.
The check boxes cover each junction road that crosses the sign's road and does not come from the same road. They are cubes 0.9 of a lane wide, along the road. They also reach back along the lanes before it, for 0.1 s at the road's speed.
The delayed checks are a scheduled-callback queue in each sign. A timer fires on the first tick that brings it to zero or below.
Blueprints
BlueprintLibrary holds CARLA's blueprints, sorted by id. filter keeps the blueprints whose id or tag matches a shell wildcard, as fnmatch matches it. filter_by_attribute keeps the blueprints that offer a value.
An attribute keeps its value as text, as CARLA does. as_bool, as_int, as_float, as_string and as_color read it. A number reads as C's atoi and atof read it, so "3.5f" is 3.5. A modifiable attribute starts at its first recommended value.
The library has these blueprints:
- The cameras (RGB, depth, semantic and instance segmentation, optical flow, normals, DVS) and the fisheye forms of the first four.
- The three LiDARs, the radar, GNSS, IMU, collision, lane-invasion and obstacle sensors, and the two V2X senders.
- The 12 vehicles and 37 pedestrians of CARLA's catalogs.
controller.ai.walker,static.trigger.friction,static.prop.meshandutil.actor.empty.
The sensor attributes and their defaults are CARLA's. CARLA keeps its vehicle and pedestrian models as assets, and some of their data is not published. This port chooses these values:
- Five colors for every vehicle.
- A box for each vehicle base type: a car is 4.8 m long, 2.0 m wide and 1.5 m high.
- CARLA's default
VehiclePhysicsControlon four wheels in that box. - The gender "other", the generation 2 and the speeds 0, 1.4 and 2.8 m/s for each pedestrian.
Weather
WeatherParameters is data. The renderer reads it. The fields, the defaults and the 23 presets, such as ClearNoon and HardRainNight, are CARLA's. weather_preset returns a preset by name.
The sun's angles are Angles and the fog distance is a Length. The other fields are percentages or plain factors. clamped puts each field in CARLA's range. rain_screen_weight and dust_screen_weight give the strength of the rain and dust effects on a camera image.
What the port reuses
- The physics tier's
CarlaPhysicssimulates the vehicles and walkers, and its ray cast answersproject_pointandground_projection. mesh_factory.generate_meshgives the road and sidewalk colliders.map.Mapgives the signals, the controllers, the lanes and the topology.- A box in the world is a
math.obb.OBB, andintersects_obbtests a vehicle against a light's or a sign's box. - An actor's box is a
bounding_box.BoundingBox. A pose is atransform.CarlaTransform. - A color attribute reads as a
render.framebuffer.Color, and a semantic tag is asensor.SemanticTag. math.generate_rangewalks a signal's lanes, as CARLA'sMath::GenerateRangedoes.
Differences from CARLA
- There is no server and no client, so a method returns at once. A world has no asynchronous mode and no wall clock.
tickneedsfixed_delta_seconds. - A spawn checks only against vehicles and walkers, not against the road or props.
- A vehicle or a walker cannot have a parent. A spring-arm attachment follows its parent rigidly.
- The physics world cannot remove a body. A destroyed vehicle's or walker's body waits far below the map, with no collisions.
- Traffic lights, signs and the spectator cannot be destroyed. The children of a destroyed actor stay where they were.
- An actor without a body reports zero velocity and zero acceleration.
- A light with no OpenDRIVE controller reads as zero in CARLA, because CARLA does not tell its controller the group. Here that light reports its state.
- CARLA
1360bb9offsets sign boxes by lane sign alone. This port uses the resolved lane direction and keeps centers inside short sections. See Trigger offset direction. - CARLA keeps the blueprints, groups and actors in hash maps. This port keeps them in a fixed order.
- A walker's bones are data only. There is no skeleton, and each bone hangs from the walker's origin.
- A hexadecimal float in an attribute reads as zero, where C reads it in full.
Not ported
- The network and the episode transport: the client and the server run in one process here.
- The on-tick callbacks, the map layers, the environment objects, the textures and the light manager.
cast_rayandset_simulate_physics, because the physics tier has no ray that passes through a hit and no way to change a body's kind.- The props of
static.prop.*and the vehicle models' meshes, which are assets. The render tier draws its own. - The RSS sensor, which needs a third-party library.
Tests
The world suites check numbers from outside the port:
tests/test_carla_world_blueprint.mojo: the wildcard,atoiandatofresults of glibc, the sensor attributes read out of CARLA's C++ by a script, and CARLA's weather table.tests/test_carla_world_signals.mojo: a cross town with lights, stop, yield and speed-limit signs. The poses of the lights and boxes are worked by hand. The light cycles and the stop sign's checks come from Python models of CARLA's C++.tests/test_carla_trigger_direction.mojo: both lane signs under right-hand and left-hand traffic, section clamps, predecessor changes, and world trigger entry and exit.tests/test_carla_world.mojo: the settings, spawning, attachments, destroying, a free fall, pushes, vehicle controls, walkers and the snapshot.