diff --git a/Docs/CHANGELOG.md b/Docs/CHANGELOG.md index 16992ad31..80506bb5d 100644 --- a/Docs/CHANGELOG.md +++ b/Docs/CHANGELOG.md @@ -1,5 +1,5 @@ ## Table of Contents -* [Latest Changes](#latest-changes) +* [CARLA ScenarioRunner 0.9.15](#carla-scenariorunner-0915) * [CARLA ScenarioRunner 0.9.13](#carla-scenariorunner-0913) * [CARLA ScenarioRunner 0.9.12](#carla-scenariorunner-0912) * [CARLA ScenarioRunner 0.9.11](#carla-scenariorunner-0911) @@ -12,11 +12,8 @@ * [CARLA ScenarioRunner 0.9.5](#carla-scenariorunner-095) * [CARLA ScenarioRunner 0.9.2](#carla-scenariorunner-092) -## Latest changes +## CARLA ScenarioRunner 0.9.15 ### :rocket: New Features -* Minor improvements to some example scenarios. These include FollowLeadingVehicle, VehicleTurning, DynamicObjectCrossing and SignalizedJunctionRightTurn and RunningRedLight. Their behaviors are now more smooth, robust and some outdated mechanics have been removed -* SignalizedJunctionLeftTurn has been remade. It now has an actor flow on which the ego has to merge into, instead of a single vehicle. -* The BackgroundActivity has been readded to the routes, which the objective of creating the sensation of traffic around the ego * Add waypoint reached threshold so that the precision of the actor reaching to waypoints can be adjusted based on object types. * OpenSCENARIO support: - Added both init and story support for `EntityAction` @@ -24,6 +21,91 @@ - Added both init and story support for `LateralDistanceAction` - Added support for `TrafficSignalControllerCondition` * Supported OpenSCENARIO 2.0 standard. +* New scenarios: + - InvadingTurn: vehicles at the opposite direction lane partially invade the ego's one, forcing it to leave space for them,moving slightly off-center. + - EnterActorFlow: the ego has to enter a highway lane filled with incoming traffic + - MergerIntoslowtraffic. variation of `EnterActorFlow` but with slow traffic. + - InterurbanActorFlow and InterurbanAdvancedActorFlow: actor flow scenarios for the new interurban intersections with dedicated lanes present at Town12 and Town13. + - Accident: the ego is met with an accident, forcing it to lane change to avoid it. + - AccidentTwoWays: same as `Accident` but having to invade an opposite direction lane. + - ParkedObstacle: similar to `Accident` but with a parked obstacle instead. + - ParkedObstacleTwoWays: same as `ParkedObstacle` but having to invade an opposite direction lane. + - HazardAtSideLane: similar to `Accident` but with a moving group of bicycles in the rightmost park of the lane + - HazardAtSideLaneTwoWays: same as `HazardAtSideLane` but having to invade an opposite direction lane. + - ConstructionObstacleTwoWays: same as `ConstructionObstacle` but having to invade an opposite direction lane. + - VehicleOpensDoorTwoWays: similar to `Accident` but this time the blockage is cause by a vehicle opening its door. + - StaticCutIn: the ego is meant with an adversary that exits a stopped lane, cutting in front of the ego. + - ParkingCutIn: similar to `StaticCutIn` but the adversary starts at a parking lane. + - HighwayCutIn: the ego is met with a vehicle that tries to enter the highway by cutting in front of it. + - ParkingExit: Only usable at the beginning of the routes, makes the ego start at a parking lane. + - HardBreakRoute: uses the BackgroundActivity to make all vehicles in front of the ego hard break. + - YieldToEmergencyVehicle: the ego finds an emergency vehicle behind, having to lane chane to give way. + - VehicleTurningRoutePedestrian: variation of `VehicleTurningRoute` but with a pedestrian crossing instead of a bycicle. + - BlockedIntersection: with low visibility, the ego performs a turn only to find out that the end is blocked by another vehicle. + - CrossingBicycleFlow: the ego has to do a turn at an intersection but it has to cross a bycicle lane full of traffic. + - PedestrianCrossing: a group of pedestrians crossing a crosswalk. Easier version of `DynamicObjectCrossing` with no occluder. + - ParkingCrossingPedestrian: variation of `DynamicObjectCrossing`, but using a parked vehicle as the occluder. + - OppositeVehicleTakingPriority: variation of `OppositeVehicleRunningRedLight` but without traffic lights. + - NonSignalizedJunctionLeftTurn: variation of `SignalizedJunctionLeftTurn` but without traffic lights. + - NonSignalizedJunctionRightTurn: variation of `SignalizedJunctionRightTurn` but without traffic lights. + - PriorityAtJunction: utility scenario during routes to add a green traffic light at the next intersection. + - NoSignalJunctionCrossingRoute: Does nothing but wait for the ego to exit an intersection. +* Improvements to old scenarios: + - ControlLoss: Added actual noise to the ego's control (currently only during routes). + - All VehicleTurning variations: more robustness and better synchronization. + - OppositeVehicleRunningRedLight: Improvement synchronization and the opposite vehicle's behavior. + - SignalizedJunctionLeftTurn: it is now an actor flow that ego has to cross. + - SignalizedJunctionRightTurn. it is now an actor flow that the ego has to merge into. + - Renamed `ConstructionSetupCrossing` to `ConstructionObstacle`, and prepared it for routes. +* Improvements to the CarlaDataProvider: + - Added a lock when checking the dictionaries to avoid issues in multithreading + - Added the `transform` argument to all register function to avoid returning None during the first frame + - Added the `get_global_route_planner` and `get_all_actors` to avoid repeating these costly calls more than necessary + - Added `set_runtime_init_mode` and `is_runtime_init_mode`, used by the Leaderboard to initialize scenarios during the simulation + - At the `create_blueprint` function, replaced the `safe` argument with the `attribute_filter`, for a more generic parsing of any of the blueprint attributes. + - Removed the `CarlaDataProvider.get_ego_vehicle_route()` and `CarlaDataProvider.set_ego_vehicle_route()` functions as this is now information available to all scenarios. +* Improvements to the routes: + - Scenarios are no longer position based, but instead part of a route's xml. + - Routes now also include the criteria of its scenarios. + - `waypoint` have been renamed to `position` and are part of the `waypoints` category. + - More than one `weather` are allowed, creating a dynamic one based on the ego vehicle's completed percentage of the route. + - Changed the timeout to also be dependent on the distance driven by the ego vehicle. + - Added the `RouteLightsBehavior` to control of all scene and vehicle lights during runtime + - Added a new criteria for routes, `CheckMinSpeed`, that checks the ego's speed and compares it with the rest of the traffic + - Separated the route argument into two, `route` for the file path, and `route-id`, for the name of route. the functionality remains unchanged. + - Simplified the overall parsing. +* The BackgroundActivity part of the routes has been completely remade, with the objective of creating the sensation of traffic around the ego will increasing the performance +* Added a Backgroundmanager to interact with the new BackgroundActivity, to allow it to adapt to incoming scenarios +* Added new atomic behaviors: + - SyncArrivalWithAgent + - CutIn + - AddNoiseToRouteEgo + - ConstantVelocityAgentBehavior + - AdaptiveConstantVelocityAgentBehavior + - WaitForever + - BatchActorTransformSetter + - OppositeActorFlow + - InvadingActorFlow + - BicycleFlow + - OpenVehicleDoor + - SwitchWrongDirectionTest + - SwitchMinSpeedCriteria + - WalkerFlow + - AIWalkerBehavior + - ScenarioTimeout + - MovePedestrianWithEgo +* Improved the Criterion class for a more comprehensive base criteria and easier use in the `results_writer` class. +* Added new atomic criteria: + - MinimumSpeedRouteTest + - YieldToEmergencyVehicleTest + - ScenarioTimeoutTest +* Added new atomic trigger conditions + - WaitUntilInFrontPosition +* Merged the `Scenario` class into the `BasicScenario` one. +* Scenarios can now have parameters as part of the their xml definition, which is saved as a dictionary at `config.other_parameters` +* Simplified and improved how routes are parsed. +* Added the `wait-for-repetitions` argument at the manual control for a smoother transition between scenarios / repetitions +* Updated numpy's version to avoid issues with newer version of Python 3 ### :bug: Bug Fixes * Fixed bug at OtherLeadingVehicle scenario causing the vehicles to move faster than intended @@ -459,3 +541,4 @@ - WaitForTrafficLightState: wait for the traffic light to have a given state - SyncArrival: sync the arrival of two vehicles to a given target - AddNoiseToVehicle: Add noise to steer as well as throttle of the vehicle + - CutInWithStaticVehicle:Based on the code of ParkingCutIn,realized the cutin function of a static vehicle on the highway diff --git a/README.md b/README.md index 71122ff2a..5e6e47131 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ branch contains the latest fixes and features, and may be required to use the la It is important to also consider the release version that has to match the CARLA version. +* [Version 0.9.15](https://github.com/carla-simulator/scenario_runner/releases/tag/v0.9.15) and the 0.9.15 Branch: Compatible with [CARLA 0.9.15](https://github.com/carla-simulator/carla/releases/tag/0.9.15) * [Version 0.9.13](https://github.com/carla-simulator/scenario_runner/releases/tag/v0.9.13) and the 0.9.13 Branch: Compatible with [CARLA 0.9.13](https://github.com/carla-simulator/carla/releases/tag/0.9.13) * [Version 0.9.12](https://github.com/carla-simulator/scenario_runner/releases/tag/v0.9.12) and the 0.9.12 Branch: Compatible with [CARLA 0.9.12](https://github.com/carla-simulator/carla/releases/tag/0.9.12) * [Version 0.9.11](https://github.com/carla-simulator/scenario_runner/releases/tag/v0.9.11) and the 0.9.11 Branch: Compatible with [CARLA 0.9.11](https://github.com/carla-simulator/carla/releases/tag/0.9.11) diff --git a/manual_control.py b/manual_control.py index 4636ccb05..9a8e6237c 100755 --- a/manual_control.py +++ b/manual_control.py @@ -67,7 +67,6 @@ from pygame.locals import K_F1 from pygame.locals import KMOD_CTRL from pygame.locals import KMOD_SHIFT - from pygame.locals import K_BACKSPACE from pygame.locals import K_TAB from pygame.locals import K_SPACE from pygame.locals import K_UP @@ -114,8 +113,6 @@ def get_actor_display_name(actor, truncate=250): class World(object): - restarted = False - def __init__(self, carla_world, hud, args): self.world = carla_world try: @@ -140,10 +137,6 @@ def __init__(self, carla_world, hud, args): def restart(self, args): - if self.restarted: - return - self.restarted = True - self.player_max_speed = 1.589 self.player_max_speed_fast = 3.713 @@ -161,7 +154,7 @@ def restart(self, args): print("Ego vehicle found") self.player = vehicle break - + self.player_name = self.player.type_id # Set up the sensors. @@ -177,9 +170,14 @@ def restart(self, args): self.world.wait_for_tick() - def tick(self, clock): + def tick(self, clock, wait_for_repetitions): if len(self.world.get_actors().filter(self.player_name)) < 1: - return False + if not wait_for_repetitions: + return False + else: + self.player = None + self.destroy() + self.restart() self.hud.tick(self, clock) return True @@ -232,13 +230,6 @@ def parse_events(self, client, world, clock): elif event.type == pygame.KEYUP: if self._is_quit_shortcut(event.key): return True - elif event.key == K_BACKSPACE: - if self._autopilot_enabled: - world.player.set_autopilot(False) - world.restart() - world.player.set_autopilot(True) - else: - world.restart() elif event.key == K_F1: world.hud.toggle_info() elif event.key == K_TAB: @@ -917,7 +908,7 @@ def game_loop(args): clock.tick_busy_loop(60) if controller.parse_events(client, world, clock): return - if not world.tick(clock): + if not world.tick(clock, args.wait_for_repetitions): return world.render(display) pygame.display.flip() @@ -978,6 +969,10 @@ def main(): '--keep_ego_vehicle', action='store_true', help='do not destroy ego vehicle on exit') + argparser.add_argument( + '--wait-for-repetitions', + action='store_true', + help='Avoids stopping the manual control when the scenario ends.') args = argparser.parse_args() args.width, args.height = [int(x) for x in args.res.split('x')] diff --git a/requirements.txt b/requirements.txt index ac48631ec..fcbb5da93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ py-trees==0.8.3 +numpy==1.18.4; python_version >= '3.0' networkx==2.2 Shapely==1.7.1 psutil @@ -6,7 +7,6 @@ xmlschema==1.0.18 ephem tabulate opencv-python==4.2.0.32 -numpy matplotlib six simple-watchdog-timer diff --git a/scenario_runner.py b/scenario_runner.py index 9b31b5f5b..07e635e19 100755 --- a/scenario_runner.py +++ b/scenario_runner.py @@ -93,8 +93,8 @@ def __init__(self, args): self.client = carla.Client(args.host, int(args.port)) self.client.set_timeout(self.client_timeout) dist = pkg_resources.get_distribution("carla") - if LooseVersion(dist.version) < LooseVersion('0.9.12'): - raise ImportError("CARLA version 0.9.12 or newer required. CARLA version found: {}".format(dist)) + if LooseVersion(dist.version) < LooseVersion('0.9.15'): + raise ImportError("CARLA version 0.9.15 or newer required. CARLA version found: {}".format(dist)) # Load agent if requested via command line args # If something goes wrong an exception will be thrown by importlib (ok here) @@ -236,7 +236,10 @@ def _prepare_ego_vehicles(self, ego_vehicles): for i, _ in enumerate(self.ego_vehicles): self.ego_vehicles[i].set_transform(ego_vehicles[i].transform) - CarlaDataProvider.register_actor(self.ego_vehicles[i]) + self.ego_vehicles[i].set_target_velocity(carla.Vector3D()) + self.ego_vehicles[i].set_target_angular_velocity(carla.Vector3D()) + self.ego_vehicles[i].apply_control(carla.VehicleControl()) + CarlaDataProvider.register_actor(self.ego_vehicles[i], ego_vehicles[i].transform) # sync state if CarlaDataProvider.is_sync_mode(): @@ -401,11 +404,11 @@ def _load_and_run_scenario(self, config): timeout=100000) else: scenario_class = self._get_scenario_class_or_fail(config.type) - scenario = scenario_class(self.world, - self.ego_vehicles, - config, - self._args.randomize, - self._args.debug) + scenario = scenario_class(world=self.world, + ego_vehicles=self.ego_vehicles, + config=config, + randomize=self._args.randomize, + debug_mode=self._args.debug) except Exception as exception: # pylint: disable=broad-except print("The scenario cannot be loaded") traceback.print_exc() @@ -471,15 +474,8 @@ def _run_route(self): """ result = False - if self._args.route: - routes = self._args.route[0] - scenario_file = self._args.route[1] - single_route = None - if len(self._args.route) > 2: - single_route = self._args.route[2] - # retrieve routes - route_configurations = RouteParser.parse_routes_file(routes, scenario_file, single_route) + route_configurations = RouteParser.parse_routes_file(self._args.route, self._args.route_id) for config in route_configurations: for _ in range(self._args.repetitions): @@ -576,11 +572,10 @@ def main(): parser.add_argument('--openscenario', help='Provide an OpenSCENARIO definition') parser.add_argument('--openscenarioparams', help='Overwrited for OpenSCENARIO ParameterDeclaration') parser.add_argument('--openscenario2', help='Provide an openscenario2 definition') + parser.add_argument('--route', help='Run a route as a scenario', type=str) + parser.add_argument('--route-id', help='Run a specific route inside that \'route\' file', default='', type=str) parser.add_argument( - '--route', help='Run a route as a scenario (input: (route_file,scenario_file,[route id]))', nargs='+', type=str) - - parser.add_argument( - '--agent', help="Agent used to execute the scenario. Currently only compatible with route-based scenarios.") + '--agent', help="Agent used to execute the route. Not compatible with non-route-based scenarios.") parser.add_argument('--agentConfig', type=str, help="Path to Agent's configuration file", default="") parser.add_argument('--output', action="store_true", help='Provide results on stdout') diff --git a/srunner/data/all_towns_traffic_scenarios.json b/srunner/data/all_towns_traffic_scenarios.json deleted file mode 100644 index 8602d6896..000000000 --- a/srunner/data/all_towns_traffic_scenarios.json +++ /dev/null @@ -1,15013 +0,0 @@ -{ - "available_scenarios": [ - { - "Town01": [ - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "319.6", - "y": "-2.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "108.0", - "y": "330.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "317.9", - "y": "326.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "108.7", - "y": "199.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "317.1", - "y": "195.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "108.8", - "y": "133.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "319.8", - "y": "129.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "322.5", - "y": "55.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "170.8", - "y": "59.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.5", - "y": "316.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.5", - "y": "163.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-1.8", - "y": "163.8", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-1.6", - "y": "10.7", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "396.3", - "y": "317.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "396.3", - "y": "165.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "392.2", - "y": "12.3", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "339.0", - "y": "31.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "304.8", - "y": "199.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "305.6", - "y": "133.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "305.4", - "y": "59.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "158.1", - "y": "31.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "30.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "121.5", - "y": "55.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "121.3", - "y": "129.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "122.4", - "y": "195.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "153.9", - "y": "26.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.2", - "y": "297.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.8", - "y": "297.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "305.1", - "y": "2.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.5", - "y": "165.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.5", - "y": "100.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.5", - "y": "25.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "124.5", - "y": "2.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "58.8", - "y": "2.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "88.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "163.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "228.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "187.8", - "y": "55.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "121.6", - "y": "326.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "368.6", - "y": "326.4", - "yaw": "180.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario1" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "197.1", - "y": "195.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "197.1", - "y": "198.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "197.1", - "y": "133.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "197.1", - "y": "129.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "197.1", - "y": "326.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "197.1", - "y": "330.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "243.2", - "y": "59.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "243.2", - "y": "55.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "243.2", - "y": "1.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "243.2", - "y": "-2.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "1.8", - "y": "95.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-2.1", - "y": "95.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "1.8", - "y": "242.1", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-2.1", - "y": "242.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "392.1", - "y": "242.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "396.3", - "y": "242.1", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "392.1", - "y": "95.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "396.3", - "y": "95.5", - "yaw": "270.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario2" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "319.6", - "y": "-2.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "108.0", - "y": "330.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "317.9", - "y": "326.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "108.7", - "y": "199.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "317.1", - "y": "195.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "108.8", - "y": "133.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "319.8", - "y": "129.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "322.5", - "y": "55.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "170.8", - "y": "59.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "167.7", - "y": "1.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.5", - "y": "316.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.5", - "y": "163.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-1.8", - "y": "163.8", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-1.6", - "y": "10.7", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "396.3", - "y": "317.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "392.2", - "y": "165.1", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "396.3", - "y": "165.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "392.2", - "y": "12.3", - "yaw": "89.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario3" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "305.2", - "y": "330.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "339.0", - "y": "31.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "305.6", - "y": "133.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "305.4", - "y": "59.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "158.1", - "y": "31.4", - "yaw": "270.0", - "z": "1.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "30.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "121.5", - "y": "55.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "121.3", - "y": "129.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "122.4", - "y": "195.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "153.9", - "y": "26.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.2", - "y": "297.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.8", - "y": "297.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "305.1", - "y": "2.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.5", - "y": "165.8", - "yaw": "90.0", - "z": "1.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.5", - "y": "100.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.5", - "y": "25.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "124.5", - "y": "2.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "58.8", - "y": "2.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "88.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "163.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.5", - "y": "228.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "187.8", - "y": "55.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "121.6", - "y": "326.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "368.6", - "y": "326.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "368.1", - "y": "-2.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "338.8", - "y": "228.4", - "yaw": "270.0", - "z": "1.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "338.8", - "y": "162.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "338.8", - "y": "89.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "187.5", - "y": "-2.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "122.3", - "y": "-2.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.2", - "y": "25.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.2", - "y": "99.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.2", - "y": "165.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "124.3", - "y": "59.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "58.4", - "y": "330.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "304.8", - "y": "199.2", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario4" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario5" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario6" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario7" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "300.2", - "y": "330.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "373.1", - "y": "-2.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "338.8", - "y": "233.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "338.8", - "y": "167.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "338.8", - "y": "94.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "192.5", - "y": "-2.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "127.3", - "y": "-2.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.4", - "y": "20.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.4", - "y": "94.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.4", - "y": "160.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "119.3", - "y": "59.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "53.4", - "y": "330.6", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario8" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "338.9", - "y": "36.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "300.6", - "y": "133.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "300.4", - "y": "59.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "158.0", - "y": "36.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.4", - "y": "35.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "126.5", - "y": "55.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "126.3", - "y": "129.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "127.4", - "y": "195.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "154.1", - "y": "21.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "88.4", - "y": "292.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.7", - "y": "292.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "299.8", - "y": "199.2", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario9" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario10" - } - ], - "Town02": [ - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "180.9", - "y": "105.3", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "7.6", - "y": "109.9", - "yaw": "359.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.6", - "y": "187.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "41.4", - "y": "207.4", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "74.7", - "y": "302.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "45.7", - "y": "270.2", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "46.7", - "y": "220.7", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "136.1", - "y": "220.9", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "160.2", - "y": "191.6", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "160.2", - "y": "240.9", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "132.5", - "y": "207.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "75.3", - "y": "236.5", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "41.4", - "y": "272.3", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.3", - "y": "221.4", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "11.8", - "y": "191.6", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "102.6", - "y": "191.6", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "189.5", - "y": "157.9", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "189.4", - "y": "207.2", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "166.0", - "y": "236.9", - "yaw": "180.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario1" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "119.3", - "y": "302.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "119.3", - "y": "306.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "85.3", - "y": "105.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "85.3", - "y": "109.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.3", - "y": "243.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.3", - "y": "243.9", - "yaw": "90.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario2" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "180.9", - "y": "105.3", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "7.6", - "y": "109.9", - "yaw": "359.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario3" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "26.6", - "y": "187.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "41.4", - "y": "207.4", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "74.7", - "y": "302.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "45.7", - "y": "270.2", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "46.7", - "y": "220.7", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "136.1", - "y": "220.9", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "160.2", - "y": "191.6", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "160.2", - "y": "240.9", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "132.5", - "y": "207.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "11.6", - "y": "306.3", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "75.3", - "y": "236.5", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "41.4", - "y": "272.3", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.5", - "y": "157.3", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "75.3", - "y": "187.5", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "166.0", - "y": "187.4", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "193.6", - "y": "221.3", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "193.7", - "y": "270.7", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "102.5", - "y": "241.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.3", - "y": "221.4", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "11.8", - "y": "191.6", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "102.6", - "y": "191.6", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "189.5", - "y": "157.9", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "189.4", - "y": "207.2", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "166.0", - "y": "236.9", - "yaw": "180.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario4" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario5" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario6" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario7" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "41.9", - "y": "202.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "6.6", - "y": "306.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.4", - "y": "152.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "80.3", - "y": "187.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.0", - "y": "187.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "193.7", - "y": "226.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "193.7", - "y": "275.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "97.5", - "y": "240.9", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario8" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "187.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "45.9", - "y": "225.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "136.1", - "y": "225.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.2", - "y": "191.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.2", - "y": "240.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "132.1", - "y": "202.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "80.3", - "y": "236.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "41.9", - "y": "267.3", - "yaw": "90.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario9" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario10" - } - ], - "Town03": [ - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "231.4", - "y": "23.4", - "yaw": "91.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "234.5", - "y": "23.5", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "200.4", - "y": "62.2", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "240.0", - "y": "98.9", - "yaw": "271.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.1", - "x": "243.2", - "y": "99.0", - "yaw": "271.4", - "z": "0.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-46.1", - "y": "-140.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-74.7", - "y": "-106.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "361.3", - "x": "-78.2", - "y": "-106.3", - "yaw": "269.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.1", - "y": "-170.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.0", - "y": "-170.1", - "yaw": "93.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.2", - "y": "-28.9", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.8", - "x": "-88.4", - "y": "-28.9", - "yaw": "89.8", - "z": "-0.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-116.7", - "y": "0.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-113.4", - "y": "-135.7", - "yaw": "350.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-32.3", - "y": "-135.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.8", - "y": "-104.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "9.1", - "y": "-104.7", - "yaw": "-88.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "354.0", - "x": "37.8", - "y": "-137.8", - "yaw": "181.0", - "z": "3.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "0.6", - "y": "-167.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-3.3", - "y": "-167.9", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "7.4", - "y": "-163.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "10.6", - "y": "-163.6", - "yaw": "-88.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "41.4", - "y": "-203.9", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "41.5", - "y": "-207.0", - "yaw": "-178.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "-32.5", - "y": "-198.2", - "yaw": "1.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-32.6", - "y": "-194.8", - "yaw": "1.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.4", - "y": "113.3", - "yaw": "90.0", - "z": "0.6" - } - }, - { - "transform": { - "pitch": "1.2", - "x": "228.9", - "y": "113.2", - "yaw": "91.4", - "z": "0.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-44.8", - "y": "197.2", - "yaw": "187.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.6", - "y": "167.1", - "yaw": "78.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-87.6", - "y": "167.7", - "yaw": "438.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "52.9", - "y": "203.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "361.1", - "x": "52.9", - "y": "207.4", - "yaw": "359.9", - "z": "0.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-73.6", - "y": "165.8", - "yaw": "257.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "99.5", - "y": "-201.9", - "yaw": "184.0", - "z": "1.4" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "99.6", - "y": "-205.5", - "yaw": "-178.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "53.6", - "y": "-192.3", - "yaw": "1.0", - "z": "1.4" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "53.7", - "y": "-196.2", - "yaw": "1.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "350.0", - "x": "154.6", - "y": "-164.4", - "yaw": "270.0", - "z": "3.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "122.3", - "y": "-190.9", - "yaw": "1.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "122.4", - "y": "-194.4", - "yaw": "1.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "11.0", - "x": "52.2", - "y": "-133.3", - "yaw": "1.0", - "z": "6.5" - } - }, - { - "transform": { - "pitch": "16.0", - "x": "82.2", - "y": "-166.5", - "yaw": "93.0", - "z": "5.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "115.3", - "y": "-135.7", - "yaw": "181.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "84.1", - "y": "-103.5", - "yaw": "272.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "153.5", - "y": "-100.4", - "yaw": "270.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "12.0", - "x": "150.8", - "y": "-165.6", - "yaw": "90.0", - "z": "3.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "119.4", - "y": "-132.3", - "yaw": "1.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "114.8", - "y": "-76.5", - "yaw": "181.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "114.7", - "y": "-72.9", - "yaw": "-178.5", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "9.0", - "x": "82.9", - "y": "-47.1", - "yaw": "270.0", - "z": "5.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-117.1", - "y": "136.3", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-78.0", - "y": "165.6", - "yaw": "260.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.6", - "y": "101.7", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-1.0", - "x": "-88.1", - "y": "101.7", - "yaw": "89.8", - "z": "0.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-45.5", - "y": "131.4", - "yaw": "178.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.9", - "y": "163.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "5.7", - "y": "163.9", - "yaw": "269.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.1", - "y": "102.1", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.7", - "y": "102.1", - "yaw": "89.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "34.4", - "y": "130.8", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-37.4", - "y": "135.7", - "yaw": "358.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-145.5", - "y": "26.3", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-116.7", - "y": "-3.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-149.3", - "y": "-29.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "170.4", - "y": "99.3", - "yaw": "280.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "139.8", - "y": "62.5", - "yaw": "359.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "201.1", - "y": "58.8", - "yaw": "178.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-77.8", - "y": "34.5", - "yaw": "269.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "358.9", - "x": "-74.3", - "y": "34.5", - "yaw": "269.8", - "z": "0.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-46.2", - "y": "-2.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.7", - "y": "69.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.1", - "x": "-88.2", - "y": "69.5", - "yaw": "89.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-77.7", - "y": "69.1", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-74.2", - "y": "69.1", - "yaw": "269.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.2", - "y": "-78.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.2", - "x": "-88.6", - "y": "-78.9", - "yaw": "89.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-78.4", - "y": "-78.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.2", - "x": "-74.6", - "y": "-78.5", - "yaw": "269.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "4.9", - "y": "-78.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "8.5", - "y": "-78.4", - "yaw": "-88.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.3", - "y": "69.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.9", - "y": "69.5", - "yaw": "89.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "5.1", - "y": "69.1", - "yaw": "269.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "52.9", - "y": "196.9", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-1.1", - "x": "52.9", - "y": "193.4", - "yaw": "179.9", - "z": "0.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "235.3", - "y": "-145.9", - "yaw": "91.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "238.3", - "y": "-146.1", - "yaw": "85.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "236.4", - "y": "-53.9", - "yaw": "91.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.9", - "y": "-54.0", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "244.4", - "y": "44.3", - "yaw": "271.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "241.0", - "y": "44.2", - "yaw": "271.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "243.5", - "y": "-54.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "246.9", - "y": "-54.1", - "yaw": "271.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "1.0", - "x": "141.9", - "y": "203.7", - "yaw": "0.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "361.3", - "x": "141.9", - "y": "207.2", - "yaw": "359.9", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "245.3", - "y": "-129.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "248.8", - "y": "-129.5", - "yaw": "271.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "185.9", - "y": "-197.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "185.9", - "y": "-193.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "230.9", - "y": "-174.8", - "yaw": "65.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "227.7", - "y": "-173.4", - "yaw": "65.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "237.1", - "y": "-178.9", - "yaw": "245.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "240.0", - "y": "-180.3", - "yaw": "244.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "358.0", - "x": "160.9", - "y": "193.2", - "yaw": "179.0", - "z": "4.1" - } - }, - { - "transform": { - "pitch": "-1.3", - "x": "161.1", - "y": "196.4", - "yaw": "176.0", - "z": "2.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "235.6", - "y": "-37.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.5", - "y": "-37.2", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-37.7", - "y": "1.0", - "yaw": "7.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "32.8", - "y": "-8.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "32.7", - "y": "-4.5", - "yaw": "181.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-125.3", - "y": "45.8", - "yaw": "133.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-145.7", - "y": "92.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-115.5", - "y": "30.8", - "yaw": "134.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.8", - "y": "170.3", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.3", - "y": "170.4", - "yaw": "89.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.5", - "y": "196.7", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.1", - "x": "28.5", - "y": "193.5", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "110.7", - "y": "-7.8", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "110.6", - "y": "-3.3", - "yaw": "-179.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "352.0", - "x": "77.7", - "y": "-36.2", - "yaw": "91.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "352.0", - "x": "148.5", - "y": "-36.0", - "yaw": "91.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "182.5", - "y": "-6.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "182.4", - "y": "-2.3", - "yaw": "-179.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "199.7", - "y": "5.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "199.7", - "y": "9.5", - "yaw": "0.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-34.5", - "y": "176.8", - "yaw": "144.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario1" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "1.0", - "x": "141.9", - "y": "203.7", - "yaw": "0.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "361.3", - "x": "141.9", - "y": "207.2", - "yaw": "359.9", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "358.0", - "x": "160.9", - "y": "193.2", - "yaw": "179.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "-1.3", - "x": "161.1", - "y": "196.4", - "yaw": "176.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "245.3", - "y": "-129.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "248.8", - "y": "-129.5", - "yaw": "271.4", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario2" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-84.7", - "y": "69.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.1", - "x": "-88.2", - "y": "69.5", - "yaw": "89.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-77.7", - "y": "69.1", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-74.2", - "y": "69.1", - "yaw": "269.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.2", - "y": "-78.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.2", - "x": "-88.6", - "y": "-78.9", - "yaw": "89.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-78.4", - "y": "-78.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.2", - "x": "-74.6", - "y": "-78.5", - "yaw": "269.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "4.9", - "y": "-78.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "8.5", - "y": "-78.4", - "yaw": "-88.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.3", - "y": "69.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.9", - "y": "69.5", - "yaw": "89.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "236.4", - "y": "-53.9", - "yaw": "91.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.9", - "y": "-54.0", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "243.5", - "y": "-54.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "246.9", - "y": "-54.1", - "yaw": "271.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "46.4", - "y": "-203.4", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "46.4", - "y": "-206.6", - "yaw": "181.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario3" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "231.0", - "y": "23.6", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "240.0", - "y": "98.9", - "yaw": "271.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-46.1", - "y": "-140.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-74.7", - "y": "-106.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-78.1", - "y": "-106.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.0", - "y": "-170.1", - "yaw": "93.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-87.5", - "y": "-170.5", - "yaw": "93.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.2", - "y": "-28.9", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.8", - "x": "-88.4", - "y": "-28.9", - "yaw": "89.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-116.7", - "y": "0.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-113.4", - "y": "-135.7", - "yaw": "350.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-32.3", - "y": "-135.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.8", - "y": "-104.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "9.1", - "y": "-104.7", - "yaw": "-88.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "354.0", - "x": "37.8", - "y": "-137.8", - "yaw": "181.0", - "z": "3.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "0.6", - "y": "-167.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-3.3", - "y": "-167.9", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "7.4", - "y": "-163.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "10.6", - "y": "-163.6", - "yaw": "-88.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "41.4", - "y": "-203.9", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-32.6", - "y": "-194.8", - "yaw": "1.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.0", - "x": "53.6", - "y": "-192.3", - "yaw": "1.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "350.0", - "x": "154.6", - "y": "-164.4", - "yaw": "270.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "122.3", - "y": "-190.9", - "yaw": "1.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "11.0", - "x": "52.2", - "y": "-133.3", - "yaw": "1.0", - "z": "5.5" - } - }, - { - "transform": { - "pitch": "16.0", - "x": "82.2", - "y": "-166.5", - "yaw": "93.0", - "z": "4.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "115.3", - "y": "-135.7", - "yaw": "181.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "84.1", - "y": "-103.5", - "yaw": "272.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "153.5", - "y": "-100.4", - "yaw": "270.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "12.0", - "x": "150.8", - "y": "-165.6", - "yaw": "90.0", - "z": "2.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "114.8", - "y": "-76.5", - "yaw": "181.0", - "z": "9.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "114.7", - "y": "-72.9", - "yaw": "-178.5", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "9.0", - "x": "82.9", - "y": "-47.1", - "yaw": "270.0", - "z": "5.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-117.1", - "y": "136.3", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-78.0", - "y": "165.6", - "yaw": "260.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-73.9", - "y": "164.8", - "yaw": "258.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.6", - "y": "101.7", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-1.0", - "x": "-88.1", - "y": "101.7", - "yaw": "89.8", - "z": "0.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-45.5", - "y": "131.4", - "yaw": "178.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.9", - "y": "163.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "5.7", - "y": "163.9", - "yaw": "269.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.1", - "y": "102.1", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.7", - "y": "102.1", - "yaw": "89.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "34.4", - "y": "130.8", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-37.4", - "y": "135.7", - "yaw": "358.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-145.5", - "y": "26.3", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-116.7", - "y": "-3.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-149.3", - "y": "-29.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "170.4", - "y": "99.3", - "yaw": "280.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "139.8", - "y": "62.5", - "yaw": "359.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "201.1", - "y": "58.8", - "yaw": "178.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-74.3", - "y": "31.9", - "yaw": "270.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "359.1", - "x": "-77.8", - "y": "32.0", - "yaw": "269.8", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-46.2", - "y": "-2.9", - "yaw": "180.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "235.6", - "y": "-37.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.5", - "y": "-37.2", - "yaw": "91.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.1", - "x": "28.5", - "y": "193.5", - "yaw": "179.9", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario4" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario5" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario6" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-37.3", - "y": "-135.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.5", - "y": "-99.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "9.1", - "y": "-99.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "42.8", - "y": "-137.4", - "yaw": "180.0", - "z": "4.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "0.3", - "y": "-172.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.2", - "y": "-172.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "47.2", - "y": "-133.8", - "yaw": "0.0", - "z": "3.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "82.7", - "y": "-171.5", - "yaw": "90.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "120.3", - "y": "-135.7", - "yaw": "180.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "83.9", - "y": "-98.5", - "yaw": "270.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-122.1", - "y": "136.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-76.2", - "y": "169.8", - "yaw": "250.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-72.8", - "y": "168.8", - "yaw": "250.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.6", - "y": "96.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.1", - "y": "96.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-40.5", - "y": "131.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.2", - "y": "97.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.7", - "y": "97.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "39.4", - "y": "130.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-42.4", - "y": "134.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-74.3", - "y": "39.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-77.8", - "y": "39.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-41.2", - "y": "-2.8", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-122.1", - "y": "-0.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.0", - "y": "-48.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.4", - "y": "-48.6", - "yaw": "90.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario7" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "239.6", - "y": "103.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.0", - "y": "-33.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-121.7", - "y": "-0.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-37.3", - "y": "-135.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.5", - "y": "-99.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "42.8", - "y": "-137.4", - "yaw": "180.0", - "z": "4.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "0.3", - "y": "-172.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "46.4", - "y": "-203.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "47.2", - "y": "-133.8", - "yaw": "0.0", - "z": "3.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "82.7", - "y": "-171.5", - "yaw": "90.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "120.3", - "y": "-135.7", - "yaw": "180.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "83.9", - "y": "-98.5", - "yaw": "270.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "153.3", - "y": "-95.4", - "yaw": "270.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-122.1", - "y": "136.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-76.2", - "y": "169.8", - "yaw": "250.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-84.6", - "y": "96.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-40.5", - "y": "131.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.2", - "y": "97.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "39.4", - "y": "130.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-42.4", - "y": "134.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-149.0", - "y": "-34.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "206.1", - "y": "58.8", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-77.8", - "y": "34.1", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-41.2", - "y": "-2.8", - "yaw": "180.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario8" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "195.4", - "y": "62.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.5", - "y": "-33.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-121.7", - "y": "-0.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-37.3", - "y": "-135.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "9.0", - "y": "-99.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "42.8", - "y": "-137.4", - "yaw": "180.0", - "z": "4.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.2", - "y": "-172.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "10.4", - "y": "-158.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "47.2", - "y": "-133.8", - "yaw": "0.0", - "z": "3.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "82.7", - "y": "-171.5", - "yaw": "90.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "120.3", - "y": "-135.7", - "yaw": "180.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "83.9", - "y": "-98.5", - "yaw": "270.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "119.8", - "y": "-76.2", - "yaw": "180.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "119.7", - "y": "-72.7", - "yaw": "180.0", - "z": "8.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-122.1", - "y": "136.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-72.8", - "y": "168.8", - "yaw": "250.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.1", - "y": "96.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-40.5", - "y": "131.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.7", - "y": "97.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "39.4", - "y": "130.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-42.4", - "y": "134.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-111.7", - "y": "-3.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "168.7", - "y": "104.3", - "yaw": "290.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-74.3", - "y": "39.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-41.2", - "y": "-2.8", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "194.7", - "y": "5.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "194.7", - "y": "9.4", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario9" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-41.1", - "y": "-139.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-74.6", - "y": "-101.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-78.1", - "y": "-101.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-87.3", - "y": "-175.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.8", - "y": "-175.0", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-117.9", - "y": "-133.9", - "yaw": "-20.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario10" - } - ], - "Town04": [ - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "97.9", - "y": "-170.0", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "129.9", - "y": "-204.4", - "yaw": "105.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "160.4", - "y": "-173.3", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.2", - "y": "-170.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.4", - "y": "-173.5", - "yaw": "181.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "58.7", - "y": "-203.0", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.2", - "y": "-307.6", - "yaw": "0.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "202.2", - "y": "-341.3", - "yaw": "90.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "233.1", - "y": "-311.3", - "yaw": "179.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.8", - "y": "-277.6", - "yaw": "271.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-333.2", - "y": "435.7", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-333.0", - "y": "432.0", - "yaw": "4.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-332.7", - "y": "428.6", - "yaw": "4.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-332.5", - "y": "425.1", - "yaw": "4.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-183.2", - "y": "406.9", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-183.4", - "y": "403.5", - "yaw": "175.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-182.9", - "y": "410.5", - "yaw": "175.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-182.6", - "y": "413.9", - "yaw": "175.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "173.5", - "y": "-242.8", - "yaw": "343.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "200.9", - "y": "-276.9", - "yaw": "90.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.1", - "y": "-249.5", - "yaw": "179.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.0", - "y": "-216.3", - "yaw": "271.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "226.3", - "y": "-307.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.8", - "y": "-276.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "287.2", - "y": "-310.8", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "225.7", - "y": "-246.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.3", - "y": "-278.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "286.8", - "y": "-249.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.6", - "y": "-217.4", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "224.3", - "y": "-169.2", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-510.3", - "y": "93.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-513.7", - "y": "93.0", - "yaw": "-268.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-506.7", - "y": "93.2", - "yaw": "-268.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-503.2", - "y": "93.3", - "yaw": "-268.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.6", - "y": "-202.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-13.4", - "y": "-230.0", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-16.6", - "y": "-230.2", - "yaw": "453.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-9.7", - "y": "-229.8", - "yaw": "453.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-6.2", - "y": "-229.6", - "yaw": "453.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-487.3", - "y": "245.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-483.7", - "y": "245.8", - "yaw": "268.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-490.7", - "y": "246.1", - "yaw": "268.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-494.2", - "y": "246.2", - "yaw": "268.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "287.4", - "y": "-172.7", - "yaw": "179.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.5", - "y": "-138.5", - "yaw": "269.0", - "z": "0.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "226.4", - "y": "-120.2", - "yaw": "10.0", - "z": "0.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "254.9", - "y": "-150.7", - "yaw": "90.0", - "z": "0.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "288.3", - "y": "-121.8", - "yaw": "181.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "283.7", - "y": "-246.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.8", - "y": "-276.6", - "yaw": "80.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "344.6", - "y": "-249.3", - "yaw": "200.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "315.2", - "y": "-218.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "282.4", - "y": "-169.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "311.2", - "y": "-201.4", - "yaw": "91.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "342.7", - "y": "-172.3", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "314.4", - "y": "-140.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "281.0", - "y": "-118.1", - "yaw": "0.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.7", - "y": "-148.9", - "yaw": "90.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "340.6", - "y": "-121.7", - "yaw": "170.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "313.9", - "y": "-89.9", - "yaw": "270.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "321.5", - "y": "-168.7", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.7", - "y": "-201.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "351.3", - "y": "-137.0", - "yaw": "271.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "382.4", - "y": "-186.2", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-407.5", - "y": "26.8", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-407.4", - "y": "30.3", - "yaw": "359.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-407.4", - "y": "33.8", - "yaw": "359.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-407.4", - "y": "37.3", - "yaw": "359.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-383.2", - "y": "-19.4", - "yaw": "95.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-342.2", - "y": "16.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.4", - "x": "-342.2", - "y": "12.5", - "yaw": "-179.9", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "-0.4", - "x": "-342.2", - "y": "9.0", - "yaw": "-179.9", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "-0.4", - "x": "-342.2", - "y": "5.5", - "yaw": "-179.9", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-379.7", - "y": "-18.9", - "yaw": "95.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "13.2", - "y": "229.5", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "16.4", - "y": "229.7", - "yaw": "-87.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "9.4", - "y": "229.4", - "yaw": "-87.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.9", - "y": "229.2", - "yaw": "-87.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "15.3", - "y": "-79.9", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "11.6", - "y": "-79.9", - "yaw": "-90.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "8.1", - "y": "-79.9", - "yaw": "-90.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "4.6", - "y": "-79.9", - "yaw": "-90.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "357.0", - "x": "173.5", - "y": "36.2", - "yaw": "0.0", - "z": "9.4" - } - }, - { - "transform": { - "pitch": "357.4", - "x": "173.4", - "y": "39.3", - "yaw": "1.0", - "z": "8.4" - } - }, - { - "transform": { - "pitch": "357.4", - "x": "173.6", - "y": "32.3", - "yaw": "1.0", - "z": "8.4" - } - }, - { - "transform": { - "pitch": "357.4", - "x": "173.6", - "y": "28.8", - "yaw": "1.0", - "z": "8.4" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "6.1", - "y": "34.4", - "yaw": "0.0", - "z": "12.0" - } - }, - { - "transform": { - "pitch": "360.3", - "x": "6.1", - "y": "37.6", - "yaw": "0.2", - "z": "11.0" - } - }, - { - "transform": { - "pitch": "360.3", - "x": "6.1", - "y": "30.6", - "yaw": "0.2", - "z": "11.0" - } - }, - { - "transform": { - "pitch": "360.3", - "x": "6.1", - "y": "27.1", - "yaw": "0.2", - "z": "11.0" - } - }, - { - "transform": { - "pitch": "2.0", - "x": "-179.6", - "y": "34.3", - "yaw": "359.0", - "z": "6.8" - } - }, - { - "transform": { - "pitch": "363.0", - "x": "-179.6", - "y": "37.2", - "yaw": "0.1", - "z": "5.8" - } - }, - { - "transform": { - "pitch": "363.0", - "x": "-179.6", - "y": "30.2", - "yaw": "0.1", - "z": "5.8" - } - }, - { - "transform": { - "pitch": "363.0", - "x": "-179.6", - "y": "26.7", - "yaw": "0.1", - "z": "5.8" - } - }, - { - "transform": { - "pitch": "1.0", - "x": "-358.3", - "y": "33.5", - "yaw": "0.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-358.2", - "y": "37.0", - "yaw": "359.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-358.3", - "y": "30.0", - "yaw": "359.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-358.3", - "y": "26.5", - "yaw": "359.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-13.0", - "y": "-75.7", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-16.4", - "y": "-75.7", - "yaw": "-270.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.4", - "y": "-75.7", - "yaw": "-270.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.9", - "y": "-75.7", - "yaw": "-270.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "343.6", - "y": "14.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "1.0", - "x": "343.5", - "y": "10.6", - "yaw": "178.4", - "z": "0.4" - } - }, - { - "transform": { - "pitch": "1.0", - "x": "343.7", - "y": "17.6", - "yaw": "178.4", - "z": "0.4" - } - }, - { - "transform": { - "pitch": "1.0", - "x": "343.8", - "y": "21.1", - "yaw": "178.4", - "z": "0.4" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "156.3", - "y": "11.9", - "yaw": "180.0", - "z": "10.3" - } - }, - { - "transform": { - "pitch": "2.2", - "x": "156.3", - "y": "7.5", - "yaw": "-179.0", - "z": "9.1" - } - }, - { - "transform": { - "pitch": "2.2", - "x": "156.2", - "y": "14.5", - "yaw": "-179.0", - "z": "9.1" - } - }, - { - "transform": { - "pitch": "2.2", - "x": "156.2", - "y": "18.0", - "yaw": "-179.0", - "z": "9.1" - } - }, - { - "transform": { - "pitch": "358.0", - "x": "-206.4", - "y": "9.1", - "yaw": "179.0", - "z": "5.4" - } - }, - { - "transform": { - "pitch": "-3.0", - "x": "-206.4", - "y": "5.7", - "yaw": "-179.9", - "z": "4.4" - } - }, - { - "transform": { - "pitch": "-3.0", - "x": "-206.4", - "y": "12.7", - "yaw": "-179.9", - "z": "4.4" - } - }, - { - "transform": { - "pitch": "-3.0", - "x": "-206.4", - "y": "16.2", - "yaw": "-179.9", - "z": "4.4" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "385.9", - "y": "-235.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "382.6", - "y": "-235.0", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "389.6", - "y": "-235.2", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "393.1", - "y": "-235.2", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "409.2", - "y": "-85.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "412.6", - "y": "-85.4", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "405.6", - "y": "-85.4", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "402.1", - "y": "-85.5", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.3", - "y": "77.8", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-15.7", - "y": "77.8", - "yaw": "-270.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-8.7", - "y": "77.8", - "yaw": "-270.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.2", - "y": "77.7", - "yaw": "-270.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "12.3", - "y": "76.2", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "15.8", - "y": "76.2", - "yaw": "-90.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "8.8", - "y": "76.2", - "yaw": "-90.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "5.3", - "y": "76.2", - "yaw": "-90.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-29.6", - "y": "12.9", - "yaw": "180.0", - "z": "11.4" - } - }, - { - "transform": { - "pitch": "-1.0", - "x": "-29.6", - "y": "9.5", - "yaw": "-179.8", - "z": "10.5" - } - }, - { - "transform": { - "pitch": "-1.0", - "x": "-29.6", - "y": "6.0", - "yaw": "-179.8", - "z": "10.5" - } - }, - { - "transform": { - "pitch": "-1.0", - "x": "-29.6", - "y": "16.5", - "yaw": "-179.8", - "z": "10.5" - } - }, - { - "transform": { - "pitch": "2.0", - "x": "326.5", - "y": "20.8", - "yaw": "180.0", - "z": "1.7" - } - }, - { - "transform": { - "pitch": "1.5", - "x": "326.6", - "y": "17.4", - "yaw": "-179.0", - "z": "0.7" - } - }, - { - "transform": { - "pitch": "1.5", - "x": "326.6", - "y": "13.9", - "yaw": "-179.0", - "z": "0.7" - } - }, - { - "transform": { - "pitch": "1.5", - "x": "326.7", - "y": "10.4", - "yaw": "-179.0", - "z": "0.7" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "198.9", - "y": "-199.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "170.7", - "y": "-169.5", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.9", - "y": "-144.3", - "yaw": "250.0", - "z": "0.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "230.0", - "y": "-172.9", - "yaw": "180.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario1" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "18.9", - "yaw": "180.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "15.3", - "yaw": "180.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "11.8", - "yaw": "180.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "8.6", - "yaw": "180.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "29.6", - "yaw": "0.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "33.0", - "yaw": "0.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "36.5", - "yaw": "0.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "213.4", - "y": "40.2", - "yaw": "0.0", - "z": "6.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "331.4", - "y": "-337.1", - "yaw": "40.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "334.4", - "y": "-338.9", - "yaw": "40.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "337.3", - "y": "-341.4", - "yaw": "40.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "339.8", - "y": "-343.7", - "yaw": "40.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "346.3", - "y": "-351.9", - "yaw": "220.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "349.0", - "y": "-354.2", - "yaw": "220.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "352.4", - "y": "-356.1", - "yaw": "220.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "355.9", - "y": "-357.8", - "yaw": "220.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "95.6", - "y": "-349.7", - "yaw": "335.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "93.7", - "y": "-353.1", - "yaw": "335.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "91.9", - "y": "-355.7", - "yaw": "335.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "90.0", - "y": "-358.8", - "yaw": "335.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "86.0", - "y": "-368.4", - "yaw": "145.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "84.0", - "y": "-371.9", - "yaw": "145.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "82.2", - "y": "-374.8", - "yaw": "145.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "80.6", - "y": "-377.6", - "yaw": "145.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "15.1", - "y": "-2.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "12.0", - "y": "-2.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "8.5", - "y": "-2.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "4.9", - "y": "-2.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.9", - "y": "-2.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.0", - "y": "-2.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.4", - "y": "-2.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-16.2", - "y": "-2.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "6.2", - "yaw": "180.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "9.2", - "yaw": "180.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "12.9", - "yaw": "180.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "16.3", - "yaw": "180.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "26.9", - "yaw": "0.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "30.2", - "yaw": "0.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "34.2", - "yaw": "0.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.9", - "y": "37.5", - "yaw": "0.0", - "z": "10.8" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "37.2", - "yaw": "0.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "34.2", - "yaw": "0.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "30.1", - "yaw": "0.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "26.9", - "yaw": "0.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "6.2", - "yaw": "180.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "9.2", - "yaw": "180.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "12.9", - "yaw": "180.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-242.0", - "y": "16.3", - "yaw": "180.0", - "z": "2.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-513.8", - "y": "191.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-513.8", - "y": "191.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-511.2", - "y": "191.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-507.5", - "y": "191.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-503.9", - "y": "191.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-493.3", - "y": "191.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-490.0", - "y": "191.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-486.3", - "y": "191.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-482.6", - "y": "191.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "436.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "432.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "428.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "425.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "414.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "411.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "407.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-252.6", - "y": "404.1", - "yaw": "180.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario2" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario3" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "97.9", - "y": "-170.0", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "129.9", - "y": "-204.4", - "yaw": "105.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "160.4", - "y": "-173.3", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.2", - "y": "-170.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "92.4", - "y": "-173.5", - "yaw": "181.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "58.7", - "y": "-203.0", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.2", - "y": "-307.6", - "yaw": "0.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "202.2", - "y": "-341.3", - "yaw": "90.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "233.1", - "y": "-311.3", - "yaw": "179.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.8", - "y": "-277.6", - "yaw": "271.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "173.5", - "y": "-242.8", - "yaw": "340.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "200.9", - "y": "-276.9", - "yaw": "90.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "232.1", - "y": "-249.5", - "yaw": "179.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.0", - "y": "-216.3", - "yaw": "271.0", - "z": "1.3" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "226.3", - "y": "-307.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.8", - "y": "-276.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "287.2", - "y": "-310.8", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "225.7", - "y": "-246.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.3", - "y": "-278.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "286.8", - "y": "-249.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.6", - "y": "-217.4", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "224.3", - "y": "-169.2", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.6", - "y": "-202.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "287.4", - "y": "-172.7", - "yaw": "179.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.5", - "y": "-138.5", - "yaw": "269.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "226.1", - "y": "-119.8", - "yaw": "10.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "254.9", - "y": "-150.7", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "288.3", - "y": "-121.8", - "yaw": "181.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "283.7", - "y": "-246.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.8", - "y": "-276.6", - "yaw": "80.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "344.6", - "y": "-248.9", - "yaw": "200.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "315.2", - "y": "-218.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "282.4", - "y": "-169.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "311.2", - "y": "-201.4", - "yaw": "91.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "342.7", - "y": "-172.3", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "314.4", - "y": "-140.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "281.0", - "y": "-118.1", - "yaw": "0.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.7", - "y": "-148.9", - "yaw": "90.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "340.1", - "y": "-121.2", - "yaw": "165.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "313.9", - "y": "-89.9", - "yaw": "270.0", - "z": "1.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "321.5", - "y": "-168.7", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.7", - "y": "-201.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "351.3", - "y": "-137.0", - "yaw": "271.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "371.2", - "y": "-171.9", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "198.9", - "y": "-199.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "170.7", - "y": "-169.5", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.7", - "y": "-144.3", - "yaw": "255.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "230.0", - "y": "-172.9", - "yaw": "180.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario4" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario5" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario6" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "166.2", - "y": "-307.7", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "202.6", - "y": "-346.3", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "238.1", - "y": "-311.1", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.5", - "y": "-272.6", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "169.0", - "y": "-240.8", - "yaw": "340.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "201.2", - "y": "-281.9", - "yaw": "91.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "237.1", - "y": "-249.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.0", - "y": "-211.3", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "219.3", - "y": "-169.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.3", - "y": "-207.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "292.4", - "y": "-172.5", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.3", - "y": "-133.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "278.7", - "y": "-246.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "309.9", - "y": "-281.2", - "yaw": "75.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.7", - "y": "-245.9", - "yaw": "230.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "277.4", - "y": "-169.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "314.2", - "y": "-135.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "276.0", - "y": "-118.5", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.9", - "y": "-153.9", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "344.3", - "y": "-124.0", - "yaw": "140.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "313.7", - "y": "-84.9", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.9", - "y": "-206.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "351.4", - "y": "-132.6", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "381.8", - "y": "-176.4", - "yaw": "110.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario7" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "92.9", - "y": "-170.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.2", - "y": "-170.5", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "166.2", - "y": "-307.7", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "202.6", - "y": "-346.3", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "238.1", - "y": "-311.1", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.5", - "y": "-272.6", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "169.0", - "y": "-240.8", - "yaw": "340.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "201.2", - "y": "-281.9", - "yaw": "91.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "237.1", - "y": "-249.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.0", - "y": "-211.3", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "221.3", - "y": "-307.8", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "292.6", - "y": "-309.1", - "yaw": "190.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "219.3", - "y": "-169.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.3", - "y": "-207.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "292.4", - "y": "-172.5", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.3", - "y": "-133.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "221.5", - "y": "-121.8", - "yaw": "23.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "278.7", - "y": "-246.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "309.9", - "y": "-281.2", - "yaw": "75.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.7", - "y": "-245.9", - "yaw": "230.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "277.4", - "y": "-169.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "314.2", - "y": "-135.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "276.0", - "y": "-118.5", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.9", - "y": "-153.9", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "344.3", - "y": "-124.0", - "yaw": "140.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "313.7", - "y": "-84.9", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.9", - "y": "-206.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "351.4", - "y": "-132.6", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "381.8", - "y": "-176.4", - "yaw": "110.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario8" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "132.3", - "y": "-209.0", - "yaw": "110.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "59.2", - "y": "-208.0", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "166.2", - "y": "-307.7", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "202.6", - "y": "-346.3", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "238.1", - "y": "-311.1", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "204.5", - "y": "-272.6", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "169.0", - "y": "-240.8", - "yaw": "340.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "201.2", - "y": "-281.9", - "yaw": "91.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "237.1", - "y": "-249.6", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.0", - "y": "-211.3", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "221.3", - "y": "-307.8", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.7", - "y": "-271.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "219.3", - "y": "-169.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.3", - "y": "-207.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "292.4", - "y": "-172.5", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.3", - "y": "-133.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "254.9", - "y": "-155.8", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "278.7", - "y": "-246.4", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "309.9", - "y": "-281.2", - "yaw": "75.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.7", - "y": "-245.9", - "yaw": "230.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "277.4", - "y": "-169.1", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "314.2", - "y": "-135.5", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "276.0", - "y": "-118.5", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "310.9", - "y": "-153.9", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "344.3", - "y": "-124.0", - "yaw": "140.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "313.7", - "y": "-84.9", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "348.9", - "y": "-206.6", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "351.4", - "y": "-132.6", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "381.8", - "y": "-176.4", - "yaw": "110.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario9" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "220.7", - "y": "-246.0", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "255.3", - "y": "-283.4", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "291.8", - "y": "-250.0", - "yaw": "180.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "258.6", - "y": "-212.4", - "yaw": "270.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "199.4", - "y": "-204.5", - "yaw": "90.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "165.7", - "y": "-169.7", - "yaw": "0.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "207.2", - "y": "-140.1", - "yaw": "240.0", - "z": "1.2" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "235.4", - "y": "-172.8", - "yaw": "180.0", - "z": "1.2" - } - } - ], - "scenario_type": "Scenario10" - } - ], - "Town05": [ - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "151.4", - "y": "-26.2", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.6", - "y": "201.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.2", - "x": "-5.6", - "y": "205.1", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "-0.2", - "x": "-5.6", - "y": "208.6", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.1", - "y": "187.8", - "yaw": "178.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "64.1", - "y": "191.4", - "yaw": "180.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "64.1", - "y": "194.9", - "yaw": "180.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.6", - "y": "158.8", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "28.0", - "y": "158.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.2", - "y": "-135.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.2", - "y": "-139.0", - "yaw": "0.4", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.8", - "y": "-107.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-124.4", - "y": "-107.6", - "yaw": "270.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-265.4", - "y": "37.0", - "yaw": "268.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-268.8", - "y": "37.0", - "yaw": "-90.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-271.7", - "y": "-34.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-275.5", - "y": "-34.8", - "yaw": "90.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-238.4", - "y": "-3.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-238.4", - "y": "-0.4", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.9", - "y": "144.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.9", - "y": "147.8", - "yaw": "-179.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-155.5", - "y": "151.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-155.5", - "y": "154.5", - "yaw": "0.3", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-130.6", - "y": "116.0", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-127.2", - "y": "115.9", - "yaw": "89.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "52.7", - "y": "145.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "55.9", - "y": "-145.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "101.5", - "y": "138.0", - "yaw": "164.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "100.8", - "y": "142.5", - "yaw": "344.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "136.3", - "y": "121.4", - "yaw": "137.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "137.8", - "y": "125.7", - "yaw": "317.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "149.5", - "y": "101.1", - "yaw": "107.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "152.9", - "y": "104.2", - "yaw": "287.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "151.3", - "y": "49.7", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.5", - "y": "51.4", - "yaw": "267.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "151.4", - "y": "16.8", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.6", - "y": "-17.0", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "151.3", - "y": "-59.2", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.5", - "y": "-57.5", - "yaw": "267.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "150.5", - "y": "-102.2", - "yaw": "76.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "154.9", - "y": "-101.4", - "yaw": "255.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "134.6", - "y": "-128.0", - "yaw": "46.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "138.9", - "y": "-129.5", - "yaw": "225.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "98.3", - "y": "-141.7", - "yaw": "11.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "100.9", - "y": "-145.5", - "yaw": "190.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-150.7", - "y": "205.0", - "yaw": "0.0", - "z": "8.7" - } - }, - { - "transform": { - "pitch": "-0.6", - "x": "-151.0", - "y": "208.2", - "yaw": "5.0", - "z": "8.6" - } - }, - { - "transform": { - "pitch": "-0.6", - "x": "-150.4", - "y": "201.3", - "yaw": "5.0", - "z": "8.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.1", - "y": "98.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "206.6", - "y": "98.5", - "yaw": "-89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "210.1", - "y": "98.5", - "yaw": "-89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "1.3", - "y": "187.8", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "1.2", - "y": "191.1", - "yaw": "180.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "1.2", - "y": "194.6", - "yaw": "180.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-190.4", - "y": "-120.9", - "yaw": "100.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-193.4", - "y": "-121.9", - "yaw": "467.9", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "-58.8", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-188.1", - "y": "-58.8", - "yaw": "-90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "196.2", - "y": "-53.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "192.7", - "y": "-53.9", - "yaw": "89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "189.2", - "y": "-53.8", - "yaw": "89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.3", - "y": "-200.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "1.2", - "x": "5.3", - "y": "-204.0", - "yaw": "179.8", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "1.2", - "x": "5.2", - "y": "-207.5", - "yaw": "179.8", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-140.9", - "y": "-193.7", - "yaw": "0.0", - "z": "10.2" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-140.8", - "y": "-190.1", - "yaw": "359.4", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-140.8", - "y": "-186.6", - "yaw": "359.4", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-226.9", - "y": "75.4", - "yaw": "270.0", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-229.9", - "y": "75.3", - "yaw": "270.8", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-233.4", - "y": "75.3", - "yaw": "270.8", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-240.5", - "y": "-76.9", - "yaw": "90.0", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-244.0", - "y": "-76.8", - "yaw": "89.2", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-247.5", - "y": "-76.8", - "yaw": "89.2", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-157.2", - "y": "-91.7", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-157.2", - "y": "-95.1", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-220.8", - "y": "-88.2", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-220.8", - "y": "-84.7", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.5", - "y": "122.9", - "yaw": "258.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-187.5", - "y": "123.5", - "yaw": "258.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-155.5", - "y": "87.8", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-155.5", - "y": "84.4", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-192.1", - "y": "57.1", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-195.1", - "y": "57.1", - "yaw": "90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-223.8", - "y": "91.4", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-223.8", - "y": "94.8", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "99.1", - "y": "-33.1", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "96.5", - "y": "-33.2", - "yaw": "90.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "103.3", - "y": "33.6", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "106.5", - "y": "33.7", - "yaw": "-89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "68.4", - "y": "2.5", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "68.4", - "y": "5.4", - "yaw": "0.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "134.6", - "y": "-2.1", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-158.8", - "y": "-88.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-158.8", - "y": "-84.6", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.4", - "y": "-122.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.8", - "y": "-122.4", - "yaw": "90.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.6", - "y": "-95.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.6", - "y": "-91.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-125.0", - "y": "-56.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-121.3", - "y": "-56.5", - "yaw": "270.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-121.5", - "y": "34.7", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-124.4", - "y": "34.7", - "yaw": "-90.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.2", - "y": "-4.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.2", - "y": "-0.7", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-128.5", - "y": "-32.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-132.0", - "y": "-32.6", - "yaw": "90.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-159.2", - "y": "2.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-159.2", - "y": "6.4", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-45.8", - "y": "123.1", - "yaw": "286.2", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-50.9", - "y": "56.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.2", - "y": "56.8", - "yaw": "90.4", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.9", - "y": "91.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-83.9", - "y": "95.0", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-49.1", - "y": "121.6", - "yaw": "285.6", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.2", - "y": "84.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.2", - "y": "88.1", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.9", - "y": "25.8", - "yaw": "268.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-123.7", - "y": "123.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.1", - "y": "123.5", - "yaw": "-90.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "88.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "84.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.6", - "y": "56.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-131.2", - "y": "56.1", - "yaw": "89.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-162.3", - "y": "91.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-162.4", - "y": "94.9", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.3", - "y": "34.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-43.6", - "y": "34.6", - "yaw": "270.4", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-15.8", - "y": "-4.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-15.8", - "y": "-0.9", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-51.1", - "y": "-32.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.7", - "y": "-32.2", - "yaw": "89.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-82.2", - "y": "2.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-82.1", - "y": "6.2", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.9", - "y": "-56.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-44.3", - "y": "-56.6", - "yaw": "269.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-17.1", - "y": "-91.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-17.1", - "y": "-94.9", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-58.2", - "y": "-121.0", - "yaw": "56.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-60.9", - "y": "-119.2", - "yaw": "56.6", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.4", - "y": "-87.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-83.4", - "y": "-84.5", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.4", - "y": "123.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "123.8", - "yaw": "-90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "63.0", - "y": "84.5", - "yaw": "169.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "63.5", - "y": "87.7", - "yaw": "169.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.6", - "y": "55.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "28.1", - "y": "55.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-2.8", - "y": "91.8", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-2.8", - "y": "95.1", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "33.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "33.8", - "yaw": "-90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "62.7", - "y": "-1.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "62.7", - "y": "-5.2", - "yaw": "-179.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.0", - "y": "-32.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.8", - "y": "-32.6", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.0", - "y": "6.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-3.0", - "y": "2.6", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "32.7", - "y": "-56.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "35.9", - "y": "-56.2", - "yaw": "271.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.4", - "y": "-91.4", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.8", - "y": "-94.4", - "yaw": "-171.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "30.4", - "y": "-122.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "27.2", - "y": "-122.6", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "0.7", - "y": "-87.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "0.7", - "y": "-84.4", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-187.9", - "y": "34.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "34.8", - "yaw": "-90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.0", - "y": "-4.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.0", - "y": "-0.6", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-195.4", - "y": "-31.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-191.6", - "y": "-31.6", - "yaw": "90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-222.4", - "y": "6.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-222.4", - "y": "3.1", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "60.5", - "y": "141.7", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.6", - "y": "169.3", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.5", - "y": "169.3", - "yaw": "-90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.6", - "y": "-149.6", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "37.9", - "y": "-121.2", - "yaw": "271.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "34.2", - "y": "-121.3", - "yaw": "271.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.1", - "y": "-200.8", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.1", - "y": "-204.2", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.1", - "y": "-207.7", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "6.4", - "y": "-186.7", - "yaw": "1.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.1", - "x": "6.4", - "y": "-190.0", - "yaw": "359.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "359.1", - "x": "6.4", - "y": "-193.5", - "yaw": "359.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "38.9", - "y": "-157.2", - "yaw": "271.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "35.1", - "y": "-157.3", - "yaw": "271.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "124.4", - "y": "1.5", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario1" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-150.7", - "y": "205.0", - "yaw": "0.0", - "z": "9.7" - } - }, - { - "transform": { - "pitch": "-0.6", - "x": "-151.0", - "y": "208.2", - "yaw": "5.0", - "z": "8.6" - } - }, - { - "transform": { - "pitch": "-0.6", - "x": "-150.4", - "y": "201.3", - "yaw": "5.0", - "z": "8.6" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "203.1", - "y": "98.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "206.6", - "y": "98.5", - "yaw": "-89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "210.1", - "y": "98.5", - "yaw": "-89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "196.2", - "y": "-53.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "192.7", - "y": "-53.9", - "yaw": "89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "189.2", - "y": "-53.8", - "yaw": "89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-140.9", - "y": "-193.7", - "yaw": "0.0", - "z": "11.2" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-140.8", - "y": "-190.1", - "yaw": "359.4", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-140.8", - "y": "-186.6", - "yaw": "359.4", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-226.9", - "y": "75.4", - "yaw": "270.0", - "z": "11.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-229.9", - "y": "75.3", - "yaw": "270.8", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "360.1", - "x": "-233.4", - "y": "75.3", - "yaw": "270.8", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-244.0", - "y": "-76.8", - "yaw": "89.2", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-247.5", - "y": "-76.8", - "yaw": "89.2", - "z": "10.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-240.5", - "y": "-76.9", - "yaw": "90.0", - "z": "11.0" - } - } - ], - "scenario_type": "Scenario2" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario3" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "151.4", - "y": "-26.2", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.6", - "y": "201.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.1", - "y": "187.8", - "yaw": "178.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.6", - "y": "158.8", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "28.0", - "y": "158.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.2", - "y": "-135.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-89.4", - "y": "-142.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.8", - "y": "-107.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-124.4", - "y": "-107.6", - "yaw": "270.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-265.4", - "y": "37.0", - "yaw": "268.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-271.3", - "y": "-34.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-238.4", - "y": "-3.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-238.4", - "y": "-0.4", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.9", - "y": "144.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-155.5", - "y": "151.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-130.6", - "y": "116.0", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-127.2", - "y": "115.9", - "yaw": "89.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-190.4", - "y": "-120.9", - "yaw": "107.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-193.4", - "y": "-121.9", - "yaw": "107.9", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "-58.8", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-188.1", - "y": "-58.8", - "yaw": "-90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-157.2", - "y": "-91.7", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-157.2", - "y": "-95.1", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-220.8", - "y": "-88.2", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-220.8", - "y": "-84.7", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.5", - "y": "122.9", - "yaw": "258.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-187.5", - "y": "123.5", - "yaw": "258.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-155.5", - "y": "87.8", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-155.5", - "y": "84.4", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-192.1", - "y": "57.1", - "yaw": "88.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-195.1", - "y": "57.1", - "yaw": "90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-223.8", - "y": "91.4", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-223.8", - "y": "94.8", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "96.5", - "y": "-33.2", - "yaw": "90.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "103.3", - "y": "33.6", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "106.5", - "y": "33.7", - "yaw": "-89.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "68.4", - "y": "2.5", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "68.4", - "y": "5.4", - "yaw": "0.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "134.6", - "y": "-2.1", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "100.0", - "y": "-33.6", - "yaw": "90.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-158.8", - "y": "-88.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-158.8", - "y": "-84.6", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.4", - "y": "-122.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.8", - "y": "-122.4", - "yaw": "90.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.6", - "y": "-95.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.6", - "y": "-91.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-125.0", - "y": "-56.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-121.3", - "y": "-56.5", - "yaw": "270.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-124.9", - "y": "35.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.9", - "y": "35.4", - "yaw": "-90.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.2", - "y": "-4.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-93.2", - "y": "-0.7", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-132.2", - "y": "-32.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-128.5", - "y": "-32.6", - "yaw": "90.4", - "z": "-0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-159.2", - "y": "2.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-159.2", - "y": "6.4", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-45.8", - "y": "123.1", - "yaw": "286.2", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-50.9", - "y": "56.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.2", - "y": "56.8", - "yaw": "90.4", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.9", - "y": "91.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-83.9", - "y": "95.0", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-49.1", - "y": "121.6", - "yaw": "285.6", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-16.4", - "y": "88.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-16.4", - "y": "84.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.9", - "y": "25.8", - "yaw": "268.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-123.7", - "y": "123.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.1", - "y": "123.5", - "yaw": "-90.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "88.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "84.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.6", - "y": "56.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-131.2", - "y": "56.1", - "yaw": "89.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-162.3", - "y": "91.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-162.4", - "y": "94.9", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.3", - "y": "34.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-43.6", - "y": "34.6", - "yaw": "270.4", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-15.8", - "y": "-4.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-15.8", - "y": "-0.9", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-51.1", - "y": "-32.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.7", - "y": "-32.2", - "yaw": "89.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-82.2", - "y": "2.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-82.1", - "y": "6.2", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.9", - "y": "-56.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-44.3", - "y": "-56.6", - "yaw": "269.7", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-17.1", - "y": "-91.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-17.1", - "y": "-94.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-58.2", - "y": "-121.0", - "yaw": "56.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-60.9", - "y": "-119.2", - "yaw": "56.6", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.4", - "y": "-87.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-83.4", - "y": "-84.5", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.4", - "y": "123.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "123.8", - "yaw": "-90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "62.5", - "y": "88.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "61.9", - "y": "84.5", - "yaw": "171.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.1", - "y": "56.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "24.6", - "y": "56.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-2.8", - "y": "91.8", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-2.8", - "y": "95.1", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "33.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "33.8", - "yaw": "-90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "62.7", - "y": "-1.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "62.7", - "y": "-5.2", - "yaw": "-179.7", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.0", - "y": "-32.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.8", - "y": "-32.6", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.0", - "y": "6.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-3.0", - "y": "2.6", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "32.7", - "y": "-56.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "35.9", - "y": "-56.2", - "yaw": "271.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.4", - "y": "-91.4", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.8", - "y": "-94.4", - "yaw": "-171.3", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "30.4", - "y": "-122.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "27.2", - "y": "-122.6", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "0.7", - "y": "-87.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "0.7", - "y": "-84.4", - "yaw": "0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-187.9", - "y": "34.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "34.8", - "yaw": "-90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.0", - "y": "-4.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-156.0", - "y": "-0.6", - "yaw": "179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-191.6", - "y": "-31.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-195.1", - "y": "-31.5", - "yaw": "90.0", - "z": "0.1" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-222.3", - "y": "3.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-222.3", - "y": "6.6", - "yaw": "359.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "60.5", - "y": "141.7", - "yaw": "181.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.6", - "y": "169.3", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.6", - "y": "-149.6", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "37.9", - "y": "-121.2", - "yaw": "271.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.1", - "y": "-200.8", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "6.4", - "y": "-186.7", - "yaw": "1.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "124.4", - "y": "1.5", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario4" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario5" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario6" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-163.8", - "y": "-88.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-163.8", - "y": "-84.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.3", - "y": "-127.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.8", - "y": "-127.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.6", - "y": "-95.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.6", - "y": "-91.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-124.8", - "y": "-51.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-121.3", - "y": "-51.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-124.4", - "y": "-40.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.9", - "y": "40.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "-0.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "-4.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-128.4", - "y": "-37.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.9", - "y": "-37.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-164.2", - "y": "2.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-164.2", - "y": "6.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-51.0", - "y": "127.4", - "yaw": "290.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.8", - "y": "128.4", - "yaw": "294.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-50.7", - "y": "51.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.2", - "y": "51.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "91.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "95.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.4", - "y": "88.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.4", - "y": "84.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.9", - "y": "88.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.9", - "y": "84.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.8", - "y": "51.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.3", - "y": "51.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-167.3", - "y": "91.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-167.3", - "y": "94.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.1", - "y": "39.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-43.6", - "y": "39.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.1", - "y": "-0.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.1", - "y": "-4.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-51.2", - "y": "-37.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.7", - "y": "-37.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-87.2", - "y": "2.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-87.2", - "y": "6.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.8", - "y": "-51.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-44.3", - "y": "-51.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.1", - "y": "-91.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.1", - "y": "-94.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-59.9", - "y": "-122.8", - "yaw": "50.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-63.6", - "y": "-122.8", - "yaw": "50.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.4", - "y": "-88.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.4", - "y": "-84.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "128.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "128.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "67.1", - "y": "86.9", - "yaw": "165.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "66.2", - "y": "83.5", - "yaw": "165.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.1", - "y": "51.9", - "yaw": "95.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.6", - "y": "51.9", - "yaw": "95.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.8", - "y": "91.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.8", - "y": "95.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "38.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "38.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "67.7", - "y": "-1.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "67.7", - "y": "-5.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.4", - "y": "-37.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.9", - "y": "-37.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.7", - "y": "2.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.6", - "y": "6.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "32.3", - "y": "-51.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.8", - "y": "-51.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.4", - "y": "-89.8", - "yaw": "195.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "70.1", - "y": "-93.3", - "yaw": "195.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "30.8", - "y": "-127.5", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "27.3", - "y": "-127.5", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-4.3", - "y": "-87.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-4.3", - "y": "-84.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-188.1", - "y": "39.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "39.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-152.3", - "y": "-0.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-152.3", - "y": "-4.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-191.6", - "y": "-36.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-195.1", - "y": "-36.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-227.3", - "y": "3.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-227.3", - "y": "6.6", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario7" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-10.6", - "y": "201.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-272.0", - "y": "-39.7", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-160.6", - "y": "150.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-163.8", - "y": "-88.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.8", - "y": "-127.4", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.6", - "y": "-91.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-124.8", - "y": "-51.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-124.4", - "y": "-40.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "-0.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-128.4", - "y": "-37.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-164.2", - "y": "2.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-51.0", - "y": "127.4", - "yaw": "290.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-50.7", - "y": "51.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "91.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.4", - "y": "88.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "155.0", - "y": "30.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.1", - "y": "128.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.9", - "y": "88.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-127.8", - "y": "51.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-167.3", - "y": "91.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.1", - "y": "39.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.1", - "y": "-0.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-51.2", - "y": "-37.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-87.2", - "y": "2.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.8", - "y": "-51.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.1", - "y": "-91.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-59.5", - "y": "-122.8", - "yaw": "50.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.4", - "y": "-88.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "128.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "67.1", - "y": "86.9", - "yaw": "165.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.1", - "y": "51.9", - "yaw": "95.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.8", - "y": "91.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "31.6", - "y": "38.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "67.7", - "y": "-1.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "28.4", - "y": "-37.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.7", - "y": "2.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "32.3", - "y": "-51.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.4", - "y": "-89.8", - "yaw": "195.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "30.8", - "y": "-127.5", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-4.3", - "y": "-87.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-188.1", - "y": "39.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-152.3", - "y": "-0.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-191.6", - "y": "-36.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-227.3", - "y": "3.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "74.1", - "y": "-200.8", - "yaw": "180.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario8" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "24.6", - "y": "153.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-121.0", - "y": "-102.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-233.4", - "y": "-3.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-130.7", - "y": "111.0", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-163.8", - "y": "-84.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.3", - "y": "-127.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.6", - "y": "-95.0", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-121.3", - "y": "-51.5", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-120.9", - "y": "40.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "-4.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.9", - "y": "-37.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-164.2", - "y": "6.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-47.8", - "y": "128.4", - "yaw": "294.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.2", - "y": "51.8", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.9", - "y": "95.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.4", - "y": "84.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-123.6", - "y": "128.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-83.9", - "y": "84.5", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-131.3", - "y": "51.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-167.4", - "y": "94.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-43.6", - "y": "39.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-11.1", - "y": "-4.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-54.7", - "y": "-37.2", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-87.2", - "y": "6.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-44.3", - "y": "-51.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-12.1", - "y": "-94.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-63.6", - "y": "-122.8", - "yaw": "50.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-88.4", - "y": "-84.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "128.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "66.2", - "y": "83.5", - "yaw": "165.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.6", - "y": "51.9", - "yaw": "95.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.8", - "y": "95.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.1", - "y": "38.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "67.7", - "y": "-5.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "24.9", - "y": "-37.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-7.6", - "y": "6.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "35.8", - "y": "-51.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "70.1", - "y": "-93.3", - "yaw": "195.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "27.3", - "y": "-127.5", - "yaw": "91.5", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-4.3", - "y": "-84.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "39.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-152.3", - "y": "-4.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-195.1", - "y": "-36.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-227.3", - "y": "6.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "65.5", - "y": "142.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "69.6", - "y": "-148.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "38.5", - "y": "-152.3", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "119.4", - "y": "1.4", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario9" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-188.1", - "y": "-125.6", - "yaw": "120.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-191.2", - "y": "-127.2", - "yaw": "120.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-184.6", - "y": "-53.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-188.1", - "y": "-53.8", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-152.2", - "y": "-91.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-152.2", - "y": "-95.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-225.8", - "y": "-88.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-225.8", - "y": "-84.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-182.9", - "y": "126.9", - "yaw": "250.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-186.2", - "y": "128.0", - "yaw": "250.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-150.5", - "y": "87.9", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-150.5", - "y": "84.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-191.6", - "y": "52.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-195.1", - "y": "52.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-228.7", - "y": "91.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-228.6", - "y": "94.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "100.0", - "y": "-38.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "96.5", - "y": "-38.1", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "102.9", - "y": "38.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "106.4", - "y": "38.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "63.4", - "y": "1.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "63.4", - "y": "5.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "139.6", - "y": "-2.0", - "yaw": "180.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario10" - } - ], - "Town06": [ - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "669.4", - "y": "83.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "672.7", - "y": "83.4", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "665.7", - "y": "83.3", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "662.2", - "y": "83.3", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "658.7", - "y": "83.3", - "yaw": "270.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.3", - "y": "-53.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-8.7", - "y": "-53.6", - "yaw": "90.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.4", - "y": "20.6", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "5.8", - "y": "20.6", - "yaw": "270.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "172.6", - "y": "-16.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "172.6", - "y": "-20.1", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "172.6", - "y": "-23.6", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "172.6", - "y": "-13.1", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "44.2", - "y": "-12.7", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "44.2", - "y": "-16.1", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "44.2", - "y": "-19.6", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-178.9", - "y": "42.0", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-178.9", - "y": "45.5", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-178.9", - "y": "49.0", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-178.9", - "y": "52.5", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "37.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "41.7", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "45.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "48.7", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "52.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "9.4", - "y": "276.7", - "yaw": "269.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "12.8", - "y": "276.6", - "yaw": "-90.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.8", - "y": "276.8", - "yaw": "-90.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "239.9", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "243.1", - "yaw": "-0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "246.6", - "yaw": "-0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "250.1", - "yaw": "-0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "142.0", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "145.5", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "149.0", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.5", - "y": "152.5", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.1", - "y": "213.6", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-6.7", - "y": "213.6", - "yaw": "89.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-10.2", - "y": "213.7", - "yaw": "89.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "138.5", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "657.7", - "y": "183.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "661.1", - "y": "183.2", - "yaw": "-89.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "664.6", - "y": "183.2", - "yaw": "-89.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "668.1", - "y": "183.3", - "yaw": "-89.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "671.6", - "y": "183.3", - "yaw": "-89.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-17.2", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-20.5", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-24.0", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-13.5", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "18.9", - "y": "52.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "18.9", - "y": "48.9", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "18.9", - "y": "45.4", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "18.9", - "y": "41.9", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "455.1", - "y": "-24.1", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "455.1", - "y": "-21.0", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "455.1", - "y": "-17.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "455.1", - "y": "-14.0", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "455.1", - "y": "-10.5", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "45.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "48.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "52.2", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "41.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "608.5", - "y": "-23.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "608.5", - "y": "-20.4", - "yaw": "180.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "608.5", - "y": "-16.9", - "yaw": "180.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "608.4", - "y": "-13.4", - "yaw": "180.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "608.4", - "y": "-9.9", - "yaw": "180.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "42.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "45.3", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "48.8", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "52.3", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "38.4", - "yaw": "-1.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "42.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "45.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "48.7", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "52.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "38.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "142.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "144.9", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "148.4", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.3", - "y": "151.9", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "477.4", - "y": "137.9", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.3", - "y": "151.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.3", - "y": "147.8", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.3", - "y": "144.3", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.3", - "y": "140.8", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.3", - "y": "137.3", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "20.7", - "y": "139.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "20.5", - "y": "143.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "20.3", - "y": "146.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "20.9", - "y": "150.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "241.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "244.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "248.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "251.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "237.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "241.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "244.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "248.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "251.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "237.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "241.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "244.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "248.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "251.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "237.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-173.4", - "y": "250.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-173.4", - "y": "247.1", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-173.4", - "y": "243.6", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-173.4", - "y": "240.1", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-173.4", - "y": "236.6", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.3", - "y": "135.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-85.3", - "y": "142.4", - "yaw": "180.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-85.3", - "y": "145.9", - "yaw": "180.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-85.4", - "y": "138.6", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-21.9", - "y": "-15.6", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-21.9", - "y": "-19.4", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-21.9", - "y": "-12.4", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-21.0", - "y": "-22.5", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.2", - "y": "147.1", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.2", - "y": "143.6", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.2", - "y": "140.1", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "136.6", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "150.6", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-308.6", - "y": "250.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-308.5", - "y": "247.2", - "yaw": "2.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-308.3", - "y": "243.7", - "yaw": "2.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-308.2", - "y": "240.2", - "yaw": "2.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-369.0", - "y": "58.5", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-366.3", - "y": "58.7", - "yaw": "95.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-362.8", - "y": "59.0", - "yaw": "95.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-359.3", - "y": "59.4", - "yaw": "95.4", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-239.6", - "y": "240.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-239.6", - "y": "243.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-239.6", - "y": "247.2", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-239.6", - "y": "250.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "503.5", - "y": "-10.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "503.5", - "y": "-13.9", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "503.5", - "y": "-17.4", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "503.5", - "y": "-20.9", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "503.5", - "y": "-24.4", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "20.1", - "y": "135.9", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario1" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-305.6", - "y": "240.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-305.6", - "y": "243.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-305.6", - "y": "247.2", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-305.6", - "y": "250.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-359.5", - "y": "58.1", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-362.7", - "y": "57.7", - "yaw": "96.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-366.2", - "y": "57.3", - "yaw": "96.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-369.6", - "y": "56.9", - "yaw": "96.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-359.5", - "y": "195.1", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-362.7", - "y": "195.7", - "yaw": "96.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-366.2", - "y": "195.3", - "yaw": "96.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-369.6", - "y": "195.9", - "yaw": "96.6", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-13.9", - "yaw": "179.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-17.0", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-20.5", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "301.3", - "y": "-24.0", - "yaw": "179.8", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "-10.4", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "-13.9", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "-17.4", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "-20.9", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "-24.4", - "yaw": "-179.9", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "38.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "41.8", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "45.3", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "48.8", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "52.3", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "41.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "45.2", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "48.7", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "52.2", - "yaw": "-0.1", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.4", - "y": "138.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.4", - "y": "141.4", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.4", - "y": "144.9", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.4", - "y": "148.4", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.3", - "y": "151.9", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "137.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "140.8", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "144.3", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.4", - "y": "147.8", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "324.3", - "y": "151.3", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "136.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "140.1", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "143.6", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.3", - "y": "147.1", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "171.2", - "y": "150.6", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "237.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "241.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "244.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "248.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "333.2", - "y": "251.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "237.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "241.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "244.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "248.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "178.9", - "y": "251.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "238.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "241.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "244.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "248.0", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "26.1", - "y": "251.5", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-217.6", - "y": "-11.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-217.6", - "y": "-15.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-217.6", - "y": "-18.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-217.6", - "y": "-22.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "251.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "248.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "244.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "241.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "237.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "152.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "148.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "145.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "141.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "138.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "52.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "48.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "45.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "41.7", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "38.4", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "-10.3", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "-13.8", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "-17.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "-20.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "569.1", - "y": "-24.2", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "38.2", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "41.9", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "45.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "48.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "435.5", - "y": "52.2", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario2" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario3" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "37.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "41.7", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "45.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "48.7", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "627.0", - "y": "52.2", - "yaw": "-0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "243.3", - "yaw": "359.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "246.6", - "yaw": "-0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "250.1", - "yaw": "-0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "142.0", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "145.5", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.6", - "y": "149.0", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "626.5", - "y": "152.5", - "yaw": "0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-35.7", - "y": "239.6", - "yaw": "-0.2", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-3.1", - "y": "213.6", - "yaw": "89.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "360.0", - "x": "-6.7", - "y": "213.6", - "yaw": "89.1", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario4" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario5" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario6" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-40.7", - "y": "243.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-40.7", - "y": "239.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-10.3", - "y": "208.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.65", - "y": "282.9", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.0", - "y": "6.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.4", - "y": "6.9", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-59.5", - "y": "42.3", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-59.5", - "y": "45.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-59.5", - "y": "48.8", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.9", - "y": "101.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "2.8", - "y": "101.4", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "5.9", - "y": "25.2", - "yaw": "270.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-5.0", - "y": "-66.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-9.0", - "y": "-66.5", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.6", - "y": "-12.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.6", - "y": "-15.7", - "yaw": "180.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "64.6", - "y": "-19.8", - "yaw": "180.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario7" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-3.3", - "y": "208.6", - "yaw": "90.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-6.8", - "y": "208.6", - "yaw": "90.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario8" - }, - { - "available_event_configurations": [ - { - "transform": { - "pitch": "0.0", - "x": "-40.7", - "y": "243.1", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-40.7", - "y": "246.6", - "yaw": "0.0", - "z": "0.0" - } - }, - { - "transform": { - "pitch": "0.0", - "x": "-40.7", - "y": "250.1", - "yaw": "0.0", - "z": "0.0" - } - } - ], - "scenario_type": "Scenario9" - }, - { - "available_event_configurations": [], - "scenario_type": "Scenario10" - } - ] - } - ] -} \ No newline at end of file diff --git a/srunner/data/no_scenarios.json b/srunner/data/no_scenarios.json deleted file mode 100644 index a6e127150..000000000 --- a/srunner/data/no_scenarios.json +++ /dev/null @@ -1,6 +0,0 @@ -{ -"available_scenarios": [ -{ -} -] -} \ No newline at end of file diff --git a/srunner/data/routes_debug.xml b/srunner/data/routes_debug.xml deleted file mode 100644 index 34ef52b08..000000000 --- a/srunner/data/routes_debug.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/srunner/data/routes_devtest.xml b/srunner/data/routes_devtest.xml index acfdc5b6f..36999b881 100644 --- a/srunner/data/routes_devtest.xml +++ b/srunner/data/routes_devtest.xml @@ -1,1060 +1,588 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/srunner/data/routes_town10.xml b/srunner/data/routes_town10.xml new file mode 100644 index 000000000..ae2bd2ec9 --- /dev/null +++ b/srunner/data/routes_town10.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srunner/data/routes_training.xml b/srunner/data/routes_training.xml index 77aa76682..37224f74d 100644 --- a/srunner/data/routes_training.xml +++ b/srunner/data/routes_training.xml @@ -1,1568 +1,23903 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srunner/data/routes_validation.xml b/srunner/data/routes_validation.xml new file mode 100644 index 000000000..30ed577ad --- /dev/null +++ b/srunner/data/routes_validation.xml @@ -0,0 +1,8398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srunner/examples/ActorFlow.xml b/srunner/examples/ActorFlow.xml new file mode 100644 index 000000000..a016523a3 --- /dev/null +++ b/srunner/examples/ActorFlow.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/srunner/examples/HighwayCutIn.xml b/srunner/examples/HighwayCutIn.xml new file mode 100644 index 000000000..eda1da888 --- /dev/null +++ b/srunner/examples/HighwayCutIn.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/srunner/examples/ObjectCrossing.xml b/srunner/examples/ObjectCrossing.xml index f255403df..ba0b5f03f 100644 --- a/srunner/examples/ObjectCrossing.xml +++ b/srunner/examples/ObjectCrossing.xml @@ -51,7 +51,7 @@ - + diff --git a/srunner/examples/RouteObstacles.xml b/srunner/examples/RouteObstacles.xml new file mode 100644 index 000000000..2f157befa --- /dev/null +++ b/srunner/examples/RouteObstacles.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/srunner/examples/VehicleOpensDoor.xml b/srunner/examples/VehicleOpensDoor.xml new file mode 100644 index 000000000..111d84496 --- /dev/null +++ b/srunner/examples/VehicleOpensDoor.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/srunner/metrics/examples/criteria_filter.py b/srunner/metrics/examples/criteria_filter.py index 78938f7cb..e1eacf25e 100644 --- a/srunner/metrics/examples/criteria_filter.py +++ b/srunner/metrics/examples/criteria_filter.py @@ -37,7 +37,7 @@ def _create_metric(self, town_map, log, criteria): { "test_status": criterion["test_status"], "actual_value": criterion["actual_value"], - "success_value": criterion["expected_value_success"] + "success_value": criterion["success_value"] } } ) diff --git a/srunner/scenarioconfigs/openscenario_configuration.py b/srunner/scenarioconfigs/openscenario_configuration.py index 421c9832d..7bd1b6d6a 100644 --- a/srunner/scenarioconfigs/openscenario_configuration.py +++ b/srunner/scenarioconfigs/openscenario_configuration.py @@ -34,6 +34,8 @@ class OpenScenarioConfiguration(ScenarioConfiguration): def __init__(self, filename, client, custom_params): + super(OpenScenarioConfiguration, self).__init__() + self.xml_tree = ET.parse(filename) self.filename = filename self._custom_params = custom_params if custom_params is not None else {} diff --git a/srunner/scenarioconfigs/route_scenario_configuration.py b/srunner/scenarioconfigs/route_scenario_configuration.py index 63f4318cc..6cdf82e4b 100644 --- a/srunner/scenarioconfigs/route_scenario_configuration.py +++ b/srunner/scenarioconfigs/route_scenario_configuration.py @@ -46,5 +46,7 @@ class RouteScenarioConfiguration(ScenarioConfiguration): Basic configuration of a RouteScenario """ - trajectory = None - scenario_file = None + def __init__(self): + super(RouteScenarioConfiguration, self).__init__() + self.keypoints = None + self.scenario_configs = [] diff --git a/srunner/scenarioconfigs/scenario_configuration.py b/srunner/scenarioconfigs/scenario_configuration.py index 388a7d2cf..a6dc6aab7 100644 --- a/srunner/scenarioconfigs/scenario_configuration.py +++ b/srunner/scenarioconfigs/scenario_configuration.py @@ -61,6 +61,28 @@ def parse_from_node(node, rolename): return ActorConfigurationData(model, transform, rolename, speed, autopilot, random_location, color) + @staticmethod + def parse_from_dict(actor_dict, rolename): + """ + static method to initialize an ActorConfigurationData from a given ET tree + """ + + model = actor_dict['model'] if 'model' in actor_dict else 'vehicle.*' + + pos_x = float(actor_dict['x']) if 'x' in actor_dict else 0 + pos_y = float(actor_dict['y']) if 'y' in actor_dict else 0 + pos_z = float(actor_dict['z']) if 'z' in actor_dict else 0 + yaw = float(actor_dict['yaw']) if 'yaw' in actor_dict else 0 + transform = carla.Transform(carla.Location(x=pos_x, y=pos_y, z=pos_z), carla.Rotation(yaw=yaw)) + + rolename = actor_dict['rolename'] if 'rolename' in actor_dict else rolename + speed = actor_dict['speed'] if 'speed' in actor_dict else 0 + autopilot = actor_dict['autopilot'] if 'autopilot' in actor_dict else False + random_location = actor_dict['random_location'] if 'random_location' in actor_dict else False + color = actor_dict['color'] if 'color' in actor_dict else None + + return ActorConfigurationData(model, transform, rolename, speed, autopilot, random_location, color) + class ScenarioConfiguration(object): @@ -72,15 +94,17 @@ class ScenarioConfiguration(object): - type is the class of scenario (e.g. ControlLoss) """ - trigger_points = [] - ego_vehicles = [] - other_actors = [] - town = None - name = None - type = None - route = None - agent = None - weather = carla.WeatherParameters() - friction = None - subtype = None - route_var_name = None + def __init__(self): + self.trigger_points = [] + self.ego_vehicles = [] + self.other_actors = [] + self.other_parameters = {} + self.town = None + self.name = None + self.type = None + self.route = None + self.agent = None + self.weather = carla.WeatherParameters(sun_altitude_angle=70, cloudiness=50) + self.friction = None + self.subtype = None + self.route_var_name = None diff --git a/srunner/scenariomanager/carla_data_provider.py b/srunner/scenariomanager/carla_data_provider.py index 39ac61874..83996fbc8 100644 --- a/srunner/scenariomanager/carla_data_provider.py +++ b/srunner/scenariomanager/carla_data_provider.py @@ -14,10 +14,12 @@ import math import re +import threading from numpy import random from six import iteritems import carla +from agents.navigation.global_route_planner import GlobalRoutePlanner def calculate_velocity(actor): @@ -59,11 +61,15 @@ class CarlaDataProvider(object): # pylint: disable=too-many-public-methods _spawn_points = None _spawn_index = 0 _blueprint_library = None + _all_actors = None _ego_vehicle_route = None _traffic_manager_port = 8000 _random_seed = 2000 _rng = random.RandomState(_random_seed) _local_planner = None + _grp = None + _runtime_init_flag = False + _lock = threading.Lock() @staticmethod def set_local_planner(plan): @@ -74,28 +80,30 @@ def get_local_planner(): return CarlaDataProvider._local_planner @staticmethod - def register_actor(actor): + def register_actor(actor, transform=None): """ Add new actor to dictionaries If actor already exists, throw an exception """ - if actor in CarlaDataProvider._actor_velocity_map: - raise KeyError( - "Vehicle '{}' already registered. Cannot register twice!".format(actor.id)) - else: - CarlaDataProvider._actor_velocity_map[actor] = 0.0 - - if actor in CarlaDataProvider._actor_location_map: - raise KeyError( - "Vehicle '{}' already registered. Cannot register twice!".format(actor.id)) - else: - CarlaDataProvider._actor_location_map[actor] = None + with CarlaDataProvider._lock: + if actor in CarlaDataProvider._actor_velocity_map: + raise KeyError( + "Vehicle '{}' already registered. Cannot register twice!".format(actor.id)) + else: + CarlaDataProvider._actor_velocity_map[actor] = 0.0 + if actor in CarlaDataProvider._actor_location_map: + raise KeyError( + "Vehicle '{}' already registered. Cannot register twice!".format(actor.id)) + elif transform: + CarlaDataProvider._actor_location_map[actor] = transform.location + else: + CarlaDataProvider._actor_location_map[actor] = None - if actor in CarlaDataProvider._actor_transform_map: - raise KeyError( - "Vehicle '{}' already registered. Cannot register twice!".format(actor.id)) - else: - CarlaDataProvider._actor_transform_map[actor] = None + if actor in CarlaDataProvider._actor_transform_map: + raise KeyError( + "Vehicle '{}' already registered. Cannot register twice!".format(actor.id)) + else: + CarlaDataProvider._actor_transform_map[actor] = transform @staticmethod def update_osc_global_params(parameters): @@ -112,33 +120,39 @@ def get_osc_global_param_value(ref): return CarlaDataProvider._global_osc_parameters.get(ref.replace("$", "")) @staticmethod - def register_actors(actors): + def register_actors(actors, transforms=None): """ Add new set of actors to dictionaries """ - for actor in actors: - CarlaDataProvider.register_actor(actor) + if transforms is None: + transforms = [None] * len(actors) + + for actor, transform in zip(actors, transforms): + CarlaDataProvider.register_actor(actor, transform) @staticmethod def on_carla_tick(): """ Callback from CARLA """ - for actor in CarlaDataProvider._actor_velocity_map: - if actor is not None and actor.is_alive: - CarlaDataProvider._actor_velocity_map[actor] = calculate_velocity(actor) + with CarlaDataProvider._lock: + for actor in CarlaDataProvider._actor_velocity_map: + if actor is not None and actor.is_alive: + CarlaDataProvider._actor_velocity_map[actor] = calculate_velocity(actor) - for actor in CarlaDataProvider._actor_location_map: - if actor is not None and actor.is_alive: - CarlaDataProvider._actor_location_map[actor] = actor.get_location() + for actor in CarlaDataProvider._actor_location_map: + if actor is not None and actor.is_alive: + CarlaDataProvider._actor_location_map[actor] = actor.get_location() - for actor in CarlaDataProvider._actor_transform_map: - if actor is not None and actor.is_alive: - CarlaDataProvider._actor_transform_map[actor] = actor.get_transform() + for actor in CarlaDataProvider._actor_transform_map: + if actor is not None and actor.is_alive: + CarlaDataProvider._actor_transform_map[actor] = actor.get_transform() + + world = CarlaDataProvider._world + if world is None: + print("WARNING: CarlaDataProvider couldn't find the world") - world = CarlaDataProvider._world - if world is None: - print("WARNING: CarlaDataProvider couldn't find the world") + CarlaDataProvider._all_actors = None @staticmethod def get_velocity(actor): @@ -209,6 +223,7 @@ def set_world(world): CarlaDataProvider._sync_flag = world.get_settings().synchronous_mode CarlaDataProvider._map = world.get_map() CarlaDataProvider._blueprint_library = world.get_blueprint_library() + CarlaDataProvider._grp = GlobalRoutePlanner(CarlaDataProvider._map, 2.0) CarlaDataProvider.generate_spawn_points() CarlaDataProvider.prepare_map() @@ -238,10 +253,30 @@ def get_map(world=None): @staticmethod def get_random_seed(): """ - @return true if syncronuous mode is used + @return the random seed. """ return CarlaDataProvider._rng + @staticmethod + def get_global_route_planner(): + """ + @return the global route planner + """ + return CarlaDataProvider._grp + + @staticmethod + def get_all_actors(): + """ + @return all the world actors. This is an expensive call, hence why it is part of the CDP, + but as this might not be used by everyone, only get the actors the first time someone + calls asks for them. 'CarlaDataProvider._all_actors' is reset each tick to None. + """ + if CarlaDataProvider._all_actors: + return CarlaDataProvider._all_actors + + CarlaDataProvider._all_actors = CarlaDataProvider._world.get_actors() + return CarlaDataProvider._all_actors + @staticmethod def is_sync_mode(): """ @@ -249,6 +284,20 @@ def is_sync_mode(): """ return CarlaDataProvider._sync_flag + @staticmethod + def set_runtime_init_mode(flag): + """ + Set the runtime init mode + """ + CarlaDataProvider._runtime_init_flag = flag + + @staticmethod + def is_runtime_init_mode(): + """ + @return true if runtime init mode is used + """ + return CarlaDataProvider._runtime_init_flag + @staticmethod def find_weather_presets(): """ @@ -417,23 +466,6 @@ def get_next_traffic_light(actor, use_cached_location=True): return relevant_traffic_light - @staticmethod - def set_ego_vehicle_route(route): - """ - Set the route of the ego vehicle - - @todo extend ego_vehicle_route concept to support multi ego_vehicle scenarios - """ - CarlaDataProvider._ego_vehicle_route = route - - @staticmethod - def get_ego_vehicle_route(): - """ - returns the currently set route of the ego vehicle - Note: Can be None - """ - return CarlaDataProvider._ego_vehicle_route - @staticmethod def generate_spawn_points(): """ @@ -529,10 +561,28 @@ def get_waypoint_by_laneid(lane_num: int): return road_lanes[lane - 1] @staticmethod - def create_blueprint(model, rolename='scenario', color=None, actor_category="car", safe=False): + def create_blueprint(model, rolename='scenario', color=None, actor_category="car", attribute_filter=None): """ Function to setup the blueprint of an actor given its model and other relevant parameters """ + def check_attribute_value(blueprint, name, value): + """ + Checks if the blueprint has that attribute with that value + """ + if not blueprint.has_attribute(name): + return False + + attribute_type = blueprint.get_attribute(key).type + if attribute_type == carla.ActorAttributeType.Bool: + return blueprint.get_attribute(name).as_bool() == value + elif attribute_type == carla.ActorAttributeType.Int: + return blueprint.get_attribute(name).as_int() == value + elif attribute_type == carla.ActorAttributeType.Float: + return blueprint.get_attribute(name).as_float() == value + elif attribute_type == carla.ActorAttributeType.String: + return blueprint.get_attribute(name).as_str() == value + + return False _actor_blueprint_categories = { 'car': 'vehicle.tesla.model3', @@ -551,18 +601,11 @@ def create_blueprint(model, rolename='scenario', color=None, actor_category="car # Set the model try: blueprints = CarlaDataProvider._blueprint_library.filter(model) - blueprints_ = [] - if safe: - for bp in blueprints: - if bp.id.endswith('firetruck') or bp.id.endswith('ambulance') \ - or int(bp.get_attribute('number_of_wheels')) != 4: - # Two wheeled vehicles take much longer to render + bicicles shouldn't behave like cars - continue - blueprints_.append(bp) - else: - blueprints_ = blueprints + if attribute_filter is not None: + for key, value in attribute_filter.items(): + blueprints = [x for x in blueprints if check_attribute_value(x, key, value)] - blueprint = CarlaDataProvider._rng.choice(blueprints_) + blueprint = CarlaDataProvider._rng.choice(blueprints) except ValueError: # The model is not part of the blueprint library. Let's take a default one for the given category bp_filter = "vehicle.*" @@ -620,6 +663,8 @@ def handle_actor_batch(batch, tick=True): # Wait (or not) for the actors to be spawned properly before we do anything if not tick: pass + elif CarlaDataProvider.is_runtime_init_mode(): + CarlaDataProvider._world.wait_for_tick() elif sync_mode: CarlaDataProvider._world.tick() else: @@ -636,11 +681,11 @@ def handle_actor_batch(batch, tick=True): @staticmethod def request_new_actor(model, spawn_point, rolename='scenario', autopilot=False, random_location=False, color=None, actor_category="car", - safe_blueprint=False, tick=True): + attribute_filter=None, tick=True): """ This method tries to create a new actor, returning it if successful (None otherwise). """ - blueprint = CarlaDataProvider.create_blueprint(model, rolename, color, actor_category, safe_blueprint) + blueprint = CarlaDataProvider.create_blueprint(model, rolename, color, actor_category, attribute_filter) if random_location: actor = None @@ -649,12 +694,14 @@ def request_new_actor(model, spawn_point, rolename='scenario', autopilot=False, actor = CarlaDataProvider._world.try_spawn_actor(blueprint, spawn_point) else: - # slightly lift the actor to avoid collisions with ground when spawning the actor + # For non prop models, slightly lift the actor to avoid collisions with the ground + z_offset = 0.2 if 'prop' not in model else 0 + # DO NOT USE spawn_point directly, as this will modify spawn_point permanently _spawn_point = carla.Transform(carla.Location(), spawn_point.rotation) _spawn_point.location.x = spawn_point.location.x _spawn_point.location.y = spawn_point.location.y - _spawn_point.location.z = spawn_point.location.z + 0.2 + _spawn_point.location.z = spawn_point.location.z + z_offset actor = CarlaDataProvider._world.try_spawn_actor(blueprint, _spawn_point) if actor is None: @@ -663,7 +710,7 @@ def request_new_actor(model, spawn_point, rolename='scenario', autopilot=False, # De/activate the autopilot of the actor if it belongs to vehicle if autopilot: - if actor.type_id.startswith('vehicle.'): + if isinstance(actor, carla.Vehicle): actor.set_autopilot(autopilot, CarlaDataProvider._traffic_manager_port) else: print("WARNING: Tried to set the autopilot of a non vehicle actor") @@ -671,6 +718,8 @@ def request_new_actor(model, spawn_point, rolename='scenario', autopilot=False, # Wait for the actor to be spawned properly before we do anything if not tick: pass + elif CarlaDataProvider.is_runtime_init_mode(): + CarlaDataProvider._world.wait_for_tick() elif CarlaDataProvider.is_sync_mode(): CarlaDataProvider._world.tick() else: @@ -680,11 +729,11 @@ def request_new_actor(model, spawn_point, rolename='scenario', autopilot=False, return None CarlaDataProvider._carla_actor_pool[actor.id] = actor - CarlaDataProvider.register_actor(actor) + CarlaDataProvider.register_actor(actor, spawn_point) return actor @staticmethod - def request_new_actors(actor_list, safe_blueprint=False, tick=True): + def request_new_actors(actor_list, attribute_filter=None, tick=True): """ This method tries to series of actor in batch. If this was successful, the new actors are returned, None otherwise. @@ -709,7 +758,7 @@ def request_new_actors(actor_list, safe_blueprint=False, tick=True): # Get the blueprint blueprint = CarlaDataProvider.create_blueprint( - actor.model, actor.rolename, actor.color, actor.category, safe_blueprint) + actor.model, actor.rolename, actor.color, actor.category, attribute_filter) # Get the spawn point transform = actor.transform @@ -761,14 +810,14 @@ def request_new_actors(actor_list, safe_blueprint=False, tick=True): if actor is None: continue CarlaDataProvider._carla_actor_pool[actor.id] = actor - CarlaDataProvider.register_actor(actor) + CarlaDataProvider.register_actor(actor, _spawn_point) return actors @staticmethod def request_new_batch_actors(model, amount, spawn_points, autopilot=False, random_location=False, rolename='scenario', - safe_blueprint=False, tick=True): + attribute_filter=None, tick=True): """ Simplified version of "request_new_actors". This method also create several actors in batch. @@ -789,7 +838,7 @@ def request_new_batch_actors(model, amount, spawn_points, autopilot=False, for i in range(amount): # Get vehicle by model - blueprint = CarlaDataProvider.create_blueprint(model, rolename, safe=safe_blueprint) + blueprint = CarlaDataProvider.create_blueprint(model, rolename, attribute_filter=attribute_filter) if random_location: if CarlaDataProvider._spawn_index >= len(CarlaDataProvider._spawn_points): @@ -807,15 +856,14 @@ def request_new_batch_actors(model, amount, spawn_points, autopilot=False, if spawn_point: batch.append(SpawnActor(blueprint, spawn_point).then( - SetAutopilot(FutureActor, autopilot, - CarlaDataProvider._traffic_manager_port))) + SetAutopilot(FutureActor, autopilot, CarlaDataProvider._traffic_manager_port))) actors = CarlaDataProvider.handle_actor_batch(batch, tick) for actor in actors: if actor is None: continue CarlaDataProvider._carla_actor_pool[actor.id] = actor - CarlaDataProvider.register_actor(actor) + CarlaDataProvider.register_actor(actor, spawn_point) return actors @@ -939,11 +987,14 @@ def cleanup(): CarlaDataProvider._world = None CarlaDataProvider._sync_flag = False CarlaDataProvider._ego_vehicle_route = None + CarlaDataProvider._all_actors = None CarlaDataProvider._carla_actor_pool = {} CarlaDataProvider._client = None CarlaDataProvider._spawn_points = None CarlaDataProvider._spawn_index = 0 CarlaDataProvider._rng = random.RandomState(CarlaDataProvider._random_seed) + CarlaDataProvider._grp = None + CarlaDataProvider._runtime_init_flag = False @property def world(self): diff --git a/srunner/scenariomanager/lights_sim.py b/srunner/scenariomanager/lights_sim.py new file mode 100644 index 000000000..316def239 --- /dev/null +++ b/srunner/scenariomanager/lights_sim.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +# Copyright (c) 2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +This module provides a weather class and py_trees behavior +to simulate weather in CARLA according to the astronomic +behavior of the sun. +""" + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider + + +class RouteLightsBehavior(py_trees.behaviour.Behaviour): + + """ + Behavior responsible for turning the street lights on and off depending on the weather conditions. + Only those around the ego vehicle will be turned on, regardless of weather conditions + """ + SUN_ALTITUDE_THRESHOLD_1 = 15 + SUN_ALTITUDE_THRESHOLD_2 = 165 + + # For higher fog and cloudness values, the amount of light in scene starts to rapidly decrease + CLOUDINESS_THRESHOLD = 80 + FOG_THRESHOLD = 40 + + # In cases where more than one weather conditition is active, decrease the thresholds + COMBINED_THRESHOLD = 10 + + def __init__(self, ego_vehicle, radius=50, radius_increase=15, name="LightsBehavior"): + """ + Setup parameters + """ + super().__init__(name) + self._ego_vehicle = ego_vehicle + self._radius = radius + self._radius_increase = radius_increase + self._world = CarlaDataProvider.get_world() + self._light_manager = self._world.get_lightmanager() + self._light_manager.set_day_night_cycle(False) + self._vehicle_lights = carla.VehicleLightState.Position | carla.VehicleLightState.LowBeam + + self._prev_night_mode = False + + def update(self): + """ + Turns on / off all the lghts around a radius of the ego vehicle + """ + new_status = py_trees.common.Status.RUNNING + + location = CarlaDataProvider.get_location(self._ego_vehicle) + if not location: + return new_status + + night_mode = self._get_night_mode(self._world.get_weather()) + if night_mode: + self._turn_close_lights_on(location) + elif self._prev_night_mode: + self._turn_all_lights_off() + + self._prev_night_mode = night_mode + return new_status + + def _get_night_mode(self, weather): + """Check wheather or not the street lights need to be turned on""" + altitude_dist = weather.sun_altitude_angle - self.SUN_ALTITUDE_THRESHOLD_1 + altitude_dist = min(altitude_dist, self.SUN_ALTITUDE_THRESHOLD_2 - weather.sun_altitude_angle) + cloudiness_dist = self.CLOUDINESS_THRESHOLD - weather.cloudiness + fog_density_dist = self.FOG_THRESHOLD - weather.fog_density + + # Check each parameter independetly + if altitude_dist < 0 or cloudiness_dist < 0 or fog_density_dist < 0: + return True + + # Check if two or more values are close to their threshold + joined_threshold = int(altitude_dist < self.COMBINED_THRESHOLD) + joined_threshold += int(cloudiness_dist < self.COMBINED_THRESHOLD) + joined_threshold += int(fog_density_dist < self.COMBINED_THRESHOLD) + + if joined_threshold >= 2: + return True + + return False + + def _turn_close_lights_on(self, location): + """Turns on the lights of all the objects close to the ego vehicle""" + ego_speed = CarlaDataProvider.get_velocity(self._ego_vehicle) + radius = max(self._radius, self._radius_increase * ego_speed) + + # Street lights + on_lights = [] + off_lights = [] + + all_lights = self._light_manager.get_all_lights() + for light in all_lights: + if light.location.distance(location) > radius: + if light.is_on: + off_lights.append(light) + else: + if not light.is_on: + on_lights.append(light) + + self._light_manager.turn_on(on_lights) + self._light_manager.turn_off(off_lights) + + # Vehicles + all_vehicles = CarlaDataProvider.get_all_actors().filter('*vehicle.*') + scenario_vehicles = [v for v in all_vehicles if v.attributes['role_name'] == 'scenario'] + + for vehicle in scenario_vehicles: + if vehicle.get_location().distance(location) > radius: + lights = vehicle.get_light_state() + lights &= ~self._vehicle_lights # Remove those lights + vehicle.set_light_state(carla.VehicleLightState(lights)) + else: + lights = vehicle.get_light_state() + lights |= self._vehicle_lights # Add those lights + vehicle.set_light_state(carla.VehicleLightState(lights)) + + # Ego vehicle + lights = self._ego_vehicle.get_light_state() + lights |= self._vehicle_lights + self._ego_vehicle.set_light_state(carla.VehicleLightState(lights)) + + def _turn_all_lights_off(self): + """Turns off the lights of all object""" + all_lights = self._light_manager.get_all_lights() + off_lights = [l for l in all_lights if l.is_on] + self._light_manager.turn_off(off_lights) + + # Vehicles + all_vehicles = CarlaDataProvider.get_all_actors().filter('*vehicle.*') + scenario_vehicles = [v for v in all_vehicles if v.attributes['role_name'] == 'scenario'] + + for vehicle in scenario_vehicles: + lights = vehicle.get_light_state() + lights &= ~self._vehicle_lights # Remove those lights + vehicle.set_light_state(carla.VehicleLightState(lights)) + + # Ego vehicle + lights = self._ego_vehicle.get_light_state() + lights &= ~self._vehicle_lights # Remove those lights + self._ego_vehicle.set_light_state(carla.VehicleLightState(lights)) + + def terminate(self, new_status): + self._light_manager.set_day_night_cycle(True) + return super().terminate(new_status) \ No newline at end of file diff --git a/srunner/scenariomanager/result_writer.py b/srunner/scenariomanager/result_writer.py index b7e0dce20..7f358f7f7 100644 --- a/srunner/scenariomanager/result_writer.py +++ b/srunner/scenariomanager/result_writer.py @@ -92,32 +92,29 @@ def create_output_text(self): list_statistics = [["Start Time", "{}".format(self._start_time)]] list_statistics.extend([["End Time", "{}".format(self._end_time)]]) - list_statistics.extend([["Duration (System Time)", "{}s".format(system_time)]]) - list_statistics.extend([["Duration (Game Time)", "{}s".format(game_time)]]) - list_statistics.extend([["Ratio (System Time / Game Time)", "{}s".format(ratio)]]) + list_statistics.extend([["System Time", "{}s".format(system_time)]]) + list_statistics.extend([["Game Time", "{}s".format(game_time)]]) + list_statistics.extend([["Ratio (Game / System)", "{}".format(ratio)]]) output += tabulate(list_statistics, tablefmt='fancy_grid') output += "\n\n" # Criteria part output += " > Criteria Information\n" - header = ['Actor', 'Criterion', 'Result', 'Actual Value', 'Expected Value'] + header = ['Actor', 'Criterion', 'Result', 'Actual Value', 'Success Value'] list_statistics = [header] for criterion in self._data.scenario.get_criteria(): - name_string = criterion.name + name = criterion.name if criterion.optional: - name_string += " (Opt.)" + name += " (Opt.)" else: - name_string += " (Req.)" + name += " (Req.)" actor = "{} (id={})".format(criterion.actor.type_id[8:], criterion.actor.id) - criteria = name_string - result = "FAILURE" if criterion.test_status == "RUNNING" else criterion.test_status - actual_value = criterion.actual_value - expected_value = criterion.expected_value_success - list_statistics.extend([[actor, criteria, result, actual_value, expected_value]]) + list_statistics.extend([[ + actor, name, criterion.test_status, criterion.actual_value, criterion.success_value]]) # Timeout actor = "" @@ -182,7 +179,7 @@ def result_dict(name, actor, optional, expected, actual, success): criterion.name, "{}-{}".format(criterion.actor.type_id[8:], criterion.actor.id), criterion.optional, - criterion.expected_value_success, + criterion.success_value, criterion.actual_value, criterion.test_status in ["SUCCESS", "ACCEPTABLE"] ) @@ -255,7 +252,7 @@ def _write_to_junit(self): result_string += " Actual: {}\n".format( criterion.actual_value) result_string += " Expected: {}\n".format( - criterion.expected_value_success) + criterion.success_value) result_string += "\n" result_string += " Exact Value: {} = {}]]>\n".format( criterion.name, criterion.actual_value) diff --git a/srunner/scenariomanager/scenario_manager.py b/srunner/scenariomanager/scenario_manager.py index 62cffabf1..d832f7f8e 100644 --- a/srunner/scenariomanager/scenario_manager.py +++ b/srunner/scenariomanager/scenario_manager.py @@ -48,7 +48,6 @@ def __init__(self, debug_mode=False, sync_mode=False, timeout=2.0): """ self.scenario = None self.scenario_tree = None - self.scenario_class = None self.ego_vehicles = None self.other_actors = None @@ -103,8 +102,7 @@ def load_scenario(self, scenario, agent=None): self._agent = AgentWrapper(agent) if agent else None if self._agent is not None: self._sync_mode = True - self.scenario_class = scenario - self.scenario = scenario.scenario + self.scenario = scenario self.scenario_tree = self.scenario.scenario_tree self.ego_vehicles = scenario.ego_vehicles self.other_actors = scenario.other_actors @@ -211,11 +209,12 @@ def analyze_scenario(self, stdout, filename, junit, json): timeout = False result = "SUCCESS" - if self.scenario.test_criteria is None: + criteria = self.scenario.get_criteria() + if len(criteria) == 0: print("Nothing to analyze, this scenario has no criteria") return True - for criterion in self.scenario.get_criteria(): + for criterion in criteria: if (not criterion.optional and criterion.test_status != "SUCCESS" and criterion.test_status != "ACCEPTABLE"): diff --git a/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py b/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py index bebb7fdb0..85970a7df 100644 --- a/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py +++ b/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py @@ -31,9 +31,9 @@ import carla from agents.navigation.basic_agent import BasicAgent +from agents.navigation.constant_velocity_agent import ConstantVelocityAgent from agents.navigation.local_planner import RoadOption, LocalPlanner -from agents.navigation.global_route_planner import GlobalRoutePlanner -from agents.tools.misc import is_within_distance +from agents.tools.misc import is_within_distance, get_speed from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.carla_data_provider import calculate_velocity @@ -230,7 +230,7 @@ class ChangeWeather(AtomicBehavior): """ Atomic to write a new weather configuration into the blackboard. - Used in combination with WeatherBehavior() to have a continuous weather simulation. + Used in combination with OSCWeatherBehavior() to have a continuous weather simulation. The behavior immediately terminates with SUCCESS after updating the blackboard. @@ -292,7 +292,7 @@ def update(self): py_trees.common.Status.SUCCESS """ - for actor in CarlaDataProvider.get_world().get_actors().filter('static.trigger.friction'): + for actor in CarlaDataProvider.get_all_actors().filter('static.trigger.friction'): actor.destroy() friction_bp = CarlaDataProvider.get_world().get_blueprint_library().find('static.trigger.friction') @@ -795,7 +795,7 @@ def initialise(self): # Obtain final route, considering the routing option # At the moment everything besides "shortest" will use the CARLA GlobalPlanner - grp = GlobalRoutePlanner(CarlaDataProvider.get_map(), 2.0) + grp = CarlaDataProvider.get_global_route_planner() route = [] for i, _ in enumerate(carla_route_elements): if carla_route_elements[i][1] == "shortest": @@ -1847,7 +1847,7 @@ class KeepVelocity(AtomicBehavior): Important parameters: - actor: CARLA actor to execute the behavior - target_velocity: The target velocity the actor should reach - - forced_speed: Whether or not to forcefully set the actors speed + - forced_speed: Whether or not to forcefully set the actors speed. This will ony be active until a collision happens - duration[optional]: Duration in seconds of this behavior - distance[optional]: Maximum distance in meters covered by the actor during this behavior @@ -1866,7 +1866,8 @@ def __init__(self, actor, target_velocity, force_speed=False, self._target_velocity = target_velocity self._control, self._type = get_actor_control(actor) - self._map = self._actor.get_world().get_map() + self._world = CarlaDataProvider.get_world() + self._map = CarlaDataProvider.get_map() self._waypoint = self._map.get_waypoint(self._actor.get_location()) self._forced_speed = force_speed @@ -1910,6 +1911,9 @@ def update(self): self._actor.set_target_velocity(carla.Vector3D( math.cos(yaw) * self._target_velocity, math.sin(yaw) * self._target_velocity, 0)) + # Add a throttle. Useless speed-wise, but makes the bicycle riders pedal. + self._actor.apply_control(carla.VehicleControl(throttle=1.0)) + new_location = CarlaDataProvider.get_location(self._actor) self._distance += calculate_distance(self._location, new_location) self._location = new_location @@ -1929,13 +1933,15 @@ def terminate(self, new_status): On termination of this behavior, the throttle should be set back to 0., to avoid further acceleration. """ - - if self._type == 'vehicle': - self._control.throttle = 0.0 - elif self._type == 'walker': - self._control.speed = 0.0 - if self._actor is not None and self._actor.is_alive: - self._actor.apply_control(self._control) + try: + if self._type == 'vehicle': + self._control.throttle = 0.0 + elif self._type == 'walker': + self._control.speed = 0.0 + if self._actor is not None and self._actor.is_alive: + self._actor.apply_control(self._control) + except RuntimeError: + pass super(KeepVelocity, self).terminate(new_status) @@ -2126,6 +2132,154 @@ def terminate(self, new_status): super(SyncArrival, self).terminate(new_status) +class SyncArrivalWithAgent(AtomicBehavior): + + """ + Atomic to make two actors arrive at their corresponding places at the same time. + This uses a controller and presuposes that the actor can reach its destination by following the lane. + + The behavior is in RUNNING state until the "main" actor has reached its destination. + + Args: + actor (carla.Actor): Controlled actor. + reference_actor (carla.Actor): Reference actor to sync up to. + actor_target (carla.Transform): Endpoint of the actor after the behavior finishes. + reference_target (carla.Transform): Endpoint of the reference_actor after the behavior finishes. + delay (float): Time difference between the actors synchronization. + end_dist (float): Minimum distance from the target to finish the behavior. + name (string): Name of the behavior. + Defaults to 'SyncArrivalWithAgent'. + """ + + def __init__(self, actor, reference_actor, actor_target, reference_target, end_dist=1, + name="SyncArrivalWithAgent"): + """ + Setup required parameters + """ + super().__init__(name, actor) + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + self._actor = actor + self._actor_target = actor_target + self._reference_actor = reference_actor + self._reference_target = reference_target + self._end_dist = end_dist + self._agent = None + + def initialise(self): + """Initialises the agent""" + self._agent = ConstantVelocityAgent( + self._actor, + map_inst=CarlaDataProvider.get_map(), + grp_inst=CarlaDataProvider.get_global_route_planner()) + + def update(self): + """ + Dynamic control update for actor velocity to ensure that both actors reach their target + positions at the same time. + """ + new_status = py_trees.common.Status.RUNNING + + # Get the distance of the actor to its endpoint + distance = calculate_distance( + CarlaDataProvider.get_location(self._actor), self._actor_target.location) + + # Check if the reference actor has passed its target + if distance < self._end_dist: + ref_dir = self._reference_target.get_forward_vector() + ref_veh = self._reference_target.location - self._reference_actor.get_location() + if ref_veh.dot(ref_dir) > 0: + return py_trees.common.Status.SUCCESS + + # Get the time to arrival of the reference to its endpoint + distance_reference = calculate_distance( + CarlaDataProvider.get_location(self._reference_actor), self._reference_target.location) + + velocity_reference = CarlaDataProvider.get_velocity(self._reference_actor) + if velocity_reference > 0: + time_reference = distance_reference / velocity_reference + else: + time_reference = float('inf') + + # Get the required velocity of the actor + desired_velocity = distance / time_reference + + self._agent.set_target_speed(3.6 * desired_velocity) + self._actor.apply_control(self._agent.run_step()) + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status + + def terminate(self, new_status): + """Destroy the collision sensor of the agent""" + if self._agent: + self._agent.destroy_sensor() + return super().terminate(new_status) + + +class CutIn(AtomicBehavior): + + """ + Atomic to make an actor lane change using a Python API agent, cutting in front of another one + + The behavior creates a lane change path and is in RUNNING state until the "main" actor has finsihes it. + + Args: + actor (carla.Actor): Controlled actor. + reference_actor (carla.Actor): Reference actor to cut in. + direction (string): Side from which the cut in happens. Either 'left' or 'right'. + speed_perc (float): Percentage of the reference actor speed on which the cut in is performed. + same_lane_time (float): Amount of time spent at the same lane before cutting in. + other_lane_time (float): Amount of time spent at the other lane after cutting in. + change_time (float): Amount of time spent changing into the other + name (string): Name of the behavior. + Defaults to 'CutIn'. + """ + + def __init__(self, actor, reference_actor, direction, speed_perc=100, + same_lane_time=0, other_lane_time=0, change_time=2, + name="CutIn"): + """ + Setup required parameters + """ + super().__init__(name, actor) + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + self._reference_actor = reference_actor + self._direction = direction + self._speed_perc = speed_perc + self._same_lane_time = same_lane_time + self._other_lane_time = other_lane_time + self._change_time = change_time + + self._map = CarlaDataProvider.get_map() + self._grp = CarlaDataProvider.get_global_route_planner() + + def initialise(self): + """Initialises the agent""" + speed = CarlaDataProvider.get_velocity(self._reference_actor) + self._agent = BasicAgent( + self._actor, + 3.6 * speed * self._speed_perc / 100, + map_inst=CarlaDataProvider.get_map(), + grp_inst=CarlaDataProvider.get_global_route_planner()) + self._agent.lane_change(self._direction, self._same_lane_time, self._other_lane_time, self._change_time) + + def update(self): + """ + Dynamic control update for actor velocity to ensure that both actors reach their target + positions at the same time. + """ + new_status = py_trees.common.Status.RUNNING + if self._agent.done(): + return py_trees.common.Status.SUCCESS + + self._actor.apply_control(self._agent.run_step()) + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status + + class AddNoiseToVehicle(AtomicBehavior): """ @@ -2165,6 +2319,55 @@ def update(self): return new_status +class AddNoiseToRouteEgo(AtomicBehavior): + + """ + This class contains an atomic jitter behavior. + To add noise to steer as well as throttle of the vehicle. + + Important parameters: + - actor: CARLA actor to execute the behavior + - steer_value: Applied steering noise in [0,1] + - throttle_value: Applied throttle noise in [0,1] + + The behavior terminates after setting the new actor controls + """ + + def __init__(self, actor, throttle_mean, throttle_std, steer_mean, steer_std, name="AddNoiseToVehicle"): + """ + Setup actor , maximum steer value and throttle value + """ + super().__init__(name, actor) + self._throttle_mean = throttle_mean + self._throttle_std = throttle_std + self._steer_mean = steer_mean + self._steer_std = steer_std + + self._rng = CarlaDataProvider.get_random_seed() + + def update(self): + """ + Set steer to steer_value and throttle to throttle_value until reaching full stop + """ + new_status = py_trees.common.Status.RUNNING + + control = py_trees.blackboard.Blackboard().get("AV_control") + if not control: + print("WARNING: Couldn't add noise to the ego because the control couldn't be found") + return new_status + + throttle_noise = random.normal(self._throttle_mean, self._throttle_std) + control.throttle = max(-1, min(1, control.throttle + throttle_noise)) + + steer_noise = random.normal(self._steer_mean, self._steer_std) + control.steer = max(0, min(1, control.steer + steer_noise)) + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + self._actor.apply_control(control) + + return new_status + + class ChangeNoiseParameters(AtomicBehavior): """ @@ -2206,7 +2409,6 @@ def update(self): class BasicAgentBehavior(AtomicBehavior): - """ This class contains an atomic behavior, which uses the basic_agent from CARLA to control the actor until @@ -2218,37 +2420,108 @@ class BasicAgentBehavior(AtomicBehavior): The behavior terminates after reaching the target_location (within 2 meters) """ - def __init__(self, actor, target_location, target_speed=None, - opt_dict=None, name="BasicAgentBehavior"): + def __init__(self, actor, target_location=None, plan=None, target_speed=20, opt_dict=None, name="BasicAgentBehavior"): """ Setup actor and maximum steer value """ super(BasicAgentBehavior, self).__init__(name, actor) self._map = CarlaDataProvider.get_map() + self._target_location = target_location + self._target_speed = target_speed + self._plan = plan + + self._opt_dict = opt_dict if opt_dict else {} + self._control = carla.VehicleControl() + self._agent = None + + if self._target_location and self._plan: + raise ValueError("Choose either a destination or a plan, but not both") + + def initialise(self): + """Initialises the agent""" + self._agent = BasicAgent(self._actor, self._target_speed, opt_dict=self._opt_dict, + map_inst=CarlaDataProvider.get_map(), grp_inst=CarlaDataProvider.get_global_route_planner()) + if self._plan: + self._agent.set_global_plan(self._plan) + elif self._target_location: + init_wp = self._map.get_waypoint(CarlaDataProvider.get_location(self._actor)) + end_wp = self._map.get_waypoint(self._target_location) + self._plan = self._agent.trace_route(init_wp, end_wp) + self._agent.set_global_plan(self._plan) + + def update(self): + new_status = py_trees.common.Status.RUNNING + + if self._agent.done(): + new_status = py_trees.common.Status.SUCCESS + self._control = self._agent.run_step() + self._actor.apply_control(self._control) + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status + + def terminate(self, new_status): + """Resets the control""" + self._control.throttle = 0.0 + self._control.brake = 0.0 + self._actor.apply_control(self._control) + super(BasicAgentBehavior, self).terminate(new_status) + + +class ConstantVelocityAgentBehavior(AtomicBehavior): + + """ + This class contains an atomic behavior, which uses the + constant_velocity_agent from CARLA to control the actor until + reaching a target location. + Important parameters: + - actor: CARLA actor to execute the behavior + - target_location: Is the desired target location (carla.location), + the actor should move to + - plan: List of [carla.Waypoint, RoadOption] to pass to the controller + - target_speed: Desired speed of the actor + The behavior terminates after reaching the target_location (within 2 meters) + """ + + def __init__(self, actor, target_location, target_speed=None, + opt_dict=None, name="ConstantVelocityAgentBehavior"): + """ + Set up actor and local planner + """ + super(ConstantVelocityAgentBehavior, self).__init__(name, actor) self._target_speed = target_speed + self._map = CarlaDataProvider.get_map() self._target_location = target_location self._opt_dict = opt_dict if opt_dict else {} self._control = carla.VehicleControl() self._agent = None self._plan = None + self._map = CarlaDataProvider.get_map() + self._grp = CarlaDataProvider.get_global_route_planner() + def initialise(self): """Initialises the agent""" - self._agent = BasicAgent(self._actor, self._target_speed * 3.6, opt_dict=self._opt_dict) + self._agent = ConstantVelocityAgent( + self._actor, self._target_speed * 3.6, opt_dict=self._opt_dict, + map_inst=CarlaDataProvider.get_map(), grp_inst=CarlaDataProvider.get_global_route_planner()) self._plan = self._agent.trace_route( self._map.get_waypoint(CarlaDataProvider.get_location(self._actor)), self._map.get_waypoint(self._target_location)) self._agent.set_global_plan(self._plan) def update(self): + """Moves the actor and waits for it to finish the plan""" new_status = py_trees.common.Status.RUNNING if self._agent.done(): new_status = py_trees.common.Status.SUCCESS + self._control = self._agent.run_step() self._actor.apply_control(self._control) self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status def terminate(self, new_status): @@ -2256,8 +2529,83 @@ def terminate(self, new_status): self._control.throttle = 0.0 self._control.brake = 0.0 self._actor.apply_control(self._control) - super(BasicAgentBehavior, self).terminate(new_status) + if self._agent: + self._agent.destroy_sensor() + super(ConstantVelocityAgentBehavior, self).terminate(new_status) + +class AdaptiveConstantVelocityAgentBehavior(AtomicBehavior): + + """ + This class contains an atomic behavior, which uses the + constant_velocity_agent from CARLA to control the actor until + reaching a target location. + Important parameters: + - actor: CARLA actor to execute the behavior. + - reference_actor: Reference CARLA actor to get target speed. + - speed_increment: Float value (m/s). + How much the actor will be faster then the reference_actor. + - target_location: Is the desired target location (carla.location), + the actor should move to. + If it's None, the actor will follow the lane and never stop. + - plan: List of [carla.Waypoint, RoadOption] to pass to the controller. + The behavior terminates after reaching the target_location (within 2 meters) + """ + + def __init__(self, actor, reference_actor, target_location=None, speed_increment=10, + opt_dict=None, name="AdaptiveConstantVelocityAgentBehavior"): + """ + Set up actor and local planner + """ + super().__init__(name, actor) + self._speed_increment = speed_increment + self._reference_actor = reference_actor + self._target_location = target_location + self._opt_dict = opt_dict if opt_dict else {} + self._control = carla.VehicleControl() + self._agent = None + self._plan = None + + self._map = CarlaDataProvider.get_map() + self._grp = CarlaDataProvider.get_global_route_planner() + + def initialise(self): + """Initialises the agent""" + # Get target speed + target_speed = get_speed(self._reference_actor) + self._speed_increment * 3.6 + + self._agent = ConstantVelocityAgent(self._actor, target_speed, opt_dict=self._opt_dict, + map_inst=self._map, grp_inst=self._grp) + + if self._target_location is not None: + self._plan = self._agent.trace_route( + self._map.get_waypoint(CarlaDataProvider.get_location(self._actor)), + self._map.get_waypoint(self._target_location)) + self._agent.set_global_plan(self._plan) + + def update(self): + """Moves the actor and waits for it to finish the plan""" + new_status = py_trees.common.Status.RUNNING + target_speed = get_speed(self._reference_actor) + self._speed_increment * 3.6 + self._agent.set_target_speed(target_speed) + + if self._agent.done(): + new_status = py_trees.common.Status.SUCCESS + + self._control = self._agent.run_step() + self._actor.apply_control(self._control) + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + + return new_status + def terminate(self, new_status): + """Resets the control""" + self._control.throttle = 0.0 + self._control.brake = 0.0 + self._actor.apply_control(self._control) + if self._agent: + self._agent.destroy_sensor() + super().terminate(new_status) class Idle(AtomicBehavior): @@ -2298,6 +2646,28 @@ def update(self): return new_status +class WaitForever(AtomicBehavior): + + """ + This class contains a behavior that just waits forever. + Useful to stop some behavior sequences from stopping unwated parts of the behavior tree + + Alternatively, a parallel termination behavior has to be used to stop it. + """ + + def __init__(self, name="WaitForever"): + """ + Setup actor + """ + super().__init__(name) + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def update(self): + """ + wait forever + """ + return py_trees.common.Status.RUNNING + class WaypointFollower(AtomicBehavior): @@ -2409,7 +2779,8 @@ def _apply_local_planner(self, actor): local_planner = LocalPlanner( # pylint: disable=undefined-variable actor, opt_dict={ 'target_speed': self._target_speed * 3.6, - 'lateral_control_dict': self._args_lateral_dict}) + 'lateral_control_dict': self._args_lateral_dict, + 'max_throttle': 1.0}) if self._plan is not None: if isinstance(self._plan[0], carla.Location): @@ -2712,7 +3083,7 @@ class ActorTransformSetter(AtomicBehavior): Important parameters: - actor: CARLA actor to execute the behavior - transform: New target transform (position + orientation) of the actor - - physics [optional]: If physics is true, the actor physics will be reactivated upon success + - physics [optional]: Change the physics of the actors to true / false. To not change the physics, use None. The behavior terminates when actor is set to the new actor transform (closer than 1 meter) @@ -2749,17 +3120,54 @@ def update(self): new_status = py_trees.common.Status.FAILURE if calculate_distance(self._actor.get_location(), self._transform.location) < 1.0: - if self._physics: - self._actor.set_simulate_physics(enabled=True) + if self._physics is not None: + self._actor.set_simulate_physics(self._physics) new_status = py_trees.common.Status.SUCCESS return new_status -class TrafficLightStateSetter(AtomicBehavior): +class BatchActorTransformSetter(AtomicBehavior): """ - This class contains an atomic behavior to set the state of a given traffic light + This class contains an atomic behavior to set the transform + of an actor. + + Important parameters: + - actor_transform_list: list [carla.Actor, carla.Transform] + - physics [optional]: Change the physics of the actors to true / false. To not change the physics, use None. + + The behavior terminates immediately + """ + + def __init__(self, actor_transform_list, physics=True, name="BatchActorTransformSetter"): + """ + Init + """ + super().__init__(name) + self._actor_transform_list = actor_transform_list + self._physics = physics + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def update(self): + """ + Transform actor + """ + + for actor, transform in self._actor_transform_list: + actor.set_target_velocity(carla.Vector3D(0, 0, 0)) + actor.set_target_angular_velocity(carla.Vector3D(0, 0, 0)) + actor.set_transform(transform) + if self._physics is not None: + actor.set_simulate_physics(self._physics) + + return py_trees.common.Status.SUCCESS + + +class TrafficLightStateSetter(AtomicBehavior): + + """ + This class contains an atomic behavior to set the state of a given traffic light Args: actor (carla.TrafficLight): ID of the traffic light that shall be changed @@ -2905,7 +3313,7 @@ def __init__(self, actor_type_list, transform, threshold, blackboard_queue_name, def update(self): new_status = py_trees.common.Status.RUNNING if self._actor_limit > 0: - world_actors = self._world.get_actors() + world_actors = CarlaDataProvider.get_all_actors() spawn_point_blocked = False if (self._last_blocking_actor and self._spawn_point.location.distance(self._last_blocking_actor.get_location()) < self._threshold): @@ -2968,69 +3376,538 @@ class ActorFlow(AtomicBehavior): - sink_location (carla.Location): Location at which actors will be deleted - spawn_distance: Distance between spawned actors - sink_distance: Actors closer to the sink than this distance will be deleted + - actors_speed: Speed of the actors part of the flow [m/s] + - initial_actors: Populates all the flow trajectory at the start """ - def __init__(self, source_wp, sink_wp, spawn_dist_interval, sink_dist=2, actors_speed=30/3.6, name="ActorFlow"): + def __init__(self, source_wp, sink_wp, spawn_dist_interval, sink_dist=2, + actor_speed=20 / 3.6, initial_actors=False, initial_junction=False, name="ActorFlow"): """ Setup class members """ - super(ActorFlow, self).__init__(name) - self._rng = random.RandomState(2000) + super().__init__(name) + self._rng = CarlaDataProvider.get_random_seed() self._world = CarlaDataProvider.get_world() + self._tm = CarlaDataProvider.get_client().get_trafficmanager(CarlaDataProvider.get_traffic_manager_port()) + + self._collision_bp = self._world.get_blueprint_library().find('sensor.other.collision') + self._is_constant_velocity_active = True self._source_wp = source_wp self._sink_wp = sink_wp self._sink_location = self._sink_wp.transform.location self._source_transform = self._source_wp.transform + self._source_location = self._source_transform.location self._sink_dist = sink_dist - self._speed = actors_speed + self._speed = actor_speed + self._initial_actors = initial_actors + self._initial_junction = initial_junction self._min_spawn_dist = spawn_dist_interval[0] self._max_spawn_dist = spawn_dist_interval[1] self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) - self._actor_agent_list = [] - self._route = [] - self._grp = GlobalRoutePlanner(CarlaDataProvider.get_map(), 2.0) - self._route = self._grp.trace_route(self._source_wp.transform.location, self._sink_wp.transform.location) + self._attribute_filter = {'base_type': 'car', 'has_lights': True, 'special_type': ''} + + self._actor_list = [] + self._collision_sensor_list = [] + + self._terminated = False + + def initialise(self): + if self._initial_actors: + grp = CarlaDataProvider.get_global_route_planner() + plan = grp.trace_route(self._source_location, self._sink_location) + + ref_loc = plan[0][0].transform.location + for wp, _ in plan: + if wp.is_junction and not self._initial_junction: + continue # Spawning at junctions might break the path, so don't + if wp.transform.location.distance(ref_loc) < self._spawn_dist: + continue + self._spawn_actor(wp.transform) + ref_loc = wp.transform.location + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + def _spawn_actor(self, transform): + actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', transform, rolename='scenario', + attribute_filter=self._attribute_filter, tick=False + ) + if actor is None: + return py_trees.common.Status.RUNNING + + actor.set_autopilot(True, CarlaDataProvider.get_traffic_manager_port()) + self._tm.set_path(actor, [self._sink_location]) + self._tm.auto_lane_change(actor, False) + self._tm.set_desired_speed(actor, 3.6 * self._speed) + self._tm.update_vehicle_lights(actor, True) + + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + sensor = None + if self._is_constant_velocity_active: + self._tm.ignore_vehicles_percentage(actor, 100) + actor.enable_constant_velocity(carla.Vector3D(self._speed, 0, 0)) # For when physics are active + + sensor = self._world.spawn_actor(self._collision_bp, carla.Transform(), attach_to=actor) + sensor.listen(lambda _: self.stop_constant_velocity()) + + self._tm.ignore_lights_percentage(actor, 100) + self._tm.ignore_signs_percentage(actor, 100) + self._collision_sensor_list.append(sensor) + self._actor_list.append(actor) def update(self): """Controls the created actors and creaes / removes other when needed""" # Control the vehicles, removing them when needed - for actor_info in list(self._actor_agent_list): - actor, agent = actor_info - sink_distance = self._sink_location.distance(CarlaDataProvider.get_location(actor)) + for actor, sensor in zip(list(self._actor_list), list(self._collision_sensor_list)): + location = CarlaDataProvider.get_location(actor) + if not location: + continue + sink_distance = self._sink_location.distance(location) + if sink_distance < self._sink_dist: + if sensor is not None: + sensor.stop() + sensor.destroy() + self._collision_sensor_list.remove(sensor) + actor.destroy() + self._actor_list.remove(actor) + + # Spawn new actors if needed + if len(self._actor_list) == 0: + distance = self._spawn_dist + 1 + else: + actor_location = CarlaDataProvider.get_location(self._actor_list[-1]) + distance = self._source_location.distance(actor_location) if actor_location else 0 + + if distance > self._spawn_dist: + self._spawn_actor(self._source_transform) + + return py_trees.common.Status.RUNNING + + def stop_constant_velocity(self): + """Stops the constant velocity behavior""" + self._is_constant_velocity_active = False + for actor in self._actor_list: + actor.disable_constant_velocity() + self._tm.ignore_vehicles_percentage(actor, 0) + + def terminate(self, new_status): + """ + Default terminate. Can be extended in derived class + """ + if self._terminated: + return + + self._terminated = True + + for sensor in self._collision_sensor_list: + if sensor is None: + continue + try: + sensor.stop() + sensor.destroy() + except RuntimeError: + pass # Actor was already destroyed + + for actor in self._actor_list: + # TODO: Actors spawned in the same frame as the behavior termination won't be removed. + # Patched by removing its movement + actor.disable_constant_velocity() + actor.set_autopilot(False, CarlaDataProvider.get_traffic_manager_port()) + actor.set_target_velocity(carla.Vector3D(0,0,0)) + actor.set_target_angular_velocity(carla.Vector3D(0,0,0)) + try: + actor.destroy() + except RuntimeError: + pass # Actor was already destroyed + + +class OppositeActorFlow(AtomicBehavior): + """ + Similar to ActorFlow, but this is meant as an actor flow in the opposite direction. + As such, some configurations are different and for clarity, another behavior has been created + + Important parameters: + - source_wp (carla.Waypoint): Waypoint at which actors will be spawned + - sink_wp (carla.Waypoint): Waypoint at which actors will be deleted + - spawn_dist_interval: Distance interval between spawned actors + - sink_dist: Actors closer to the sink than this distance will be deleted + - actors_speed: Speed of the actors part of the flow [m/s] + - offset: offset from the center lane of the actors + """ + + def __init__(self, reference_wp, reference_actor, spawn_dist_interval, + time_distance=1.5, base_distance=30, sink_dist=2, name="OppositeActorFlow"): + """ + Setup class members + """ + super().__init__(name) + self._rng = CarlaDataProvider.get_random_seed() + self._world = CarlaDataProvider.get_world() + self._tm = CarlaDataProvider.get_client().get_trafficmanager(CarlaDataProvider.get_traffic_manager_port()) + + self._reference_wp = reference_wp + self._reference_actor = reference_actor + self._time_distance = time_distance + self._base_distance = base_distance + self._min_spawn_dist = spawn_dist_interval[0] + self._max_spawn_dist = spawn_dist_interval[1] + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + self._sink_dist = sink_dist + + self._attribute_filter = {'base_type': 'car', 'has_lights': True, 'special_type': ''} + + # Opposite direction needs earlier vehicle detection + self._opt_dict = {'base_vehicle_threshold': 10, 'detection_speed_ratio': 1.6} + + self._actor_list = [] + self._grp = CarlaDataProvider.get_global_route_planner() + self._map = CarlaDataProvider.get_map() + + self._terminated = False + + def _move_waypoint_forward(self, wp, distance): + """Moves forward a certain distance, stopping at junctions""" + dist = 0 + next_wp = wp + while dist < distance: + next_wps = next_wp.next(1) + if next_wps[0].is_junction: + break + next_wp = next_wps[0] + dist += 1 + + return next_wp + + def _move_waypoint_backwards(self, wp, distance): + """Moves backwards a certain distance, stopping at junctions""" + dist = 0 + prev_wp = wp + while dist < distance: + prev_wps = prev_wp.previous(1) + if prev_wps[0].is_junction: + break + prev_wp = prev_wps[0] + dist += 1 + + return prev_wp + + def initialise(self): + """Get the actor flow source and sink, depending on the reference actor speed""" + self._speed = self._reference_actor.get_speed_limit() # Km / h + self._flow_distance = self._time_distance * self._speed + self._base_distance + + self._sink_wp = self._move_waypoint_forward(self._reference_wp, self._flow_distance) + self._source_wp = self._move_waypoint_backwards(self._reference_wp, self._flow_distance) + + self._source_transform = self._source_wp.transform + self._source_location = self._source_transform.location + self._sink_location = self._sink_wp.transform.location + + self._route = self._grp.trace_route(self._source_location, self._sink_location) + + return super().initialise() + + def _spawn_actor(self): + actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', self._source_transform, rolename='scenario', + attribute_filter=self._attribute_filter, tick=False + ) + if actor is None: + return py_trees.common.Status.RUNNING + + controller = BasicAgent(actor, self._speed, self._opt_dict, self._map, self._grp) + controller.set_global_plan(self._route) + self._actor_list.append([actor, controller]) + + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + def update(self): + """Controls the created actors and creates / removes other when needed""" + # Control the vehicles, removing them when needed + for actor_data in list(self._actor_list): + actor, controller = actor_data + location = CarlaDataProvider.get_location(actor) + if not location: + continue + sink_distance = self._sink_location.distance(location) if sink_distance < self._sink_dist: actor.destroy() - self._actor_agent_list.remove(actor_info) + self._actor_list.remove(actor_data) else: - control = agent.run_step() - actor.apply_control(control) + actor.apply_control(controller.run_step()) # Spawn new actors if needed - if len(self._actor_agent_list) == 0: + if len(self._actor_list) == 0: distance = self._spawn_dist + 1 else: - actor_location = CarlaDataProvider.get_location(self._actor_agent_list[-1][0]) - distance = self._source_transform.location.distance(actor_location) + actor_location = CarlaDataProvider.get_location(self._actor_list[-1][0]) + distance = self._source_location.distance(actor_location) if actor_location else 0 + if distance > self._spawn_dist: + self._spawn_actor() + return py_trees.common.Status.RUNNING + + def terminate(self, new_status): + """ + Default terminate. Can be extended in derived class + """ + if self._terminated: + return + + self._terminated = True + + for actor, _ in self._actor_list: + # TODO: Actors spawned in the same frame as the behavior termination won't be removed. + # Patched by removing its movement + actor.disable_constant_velocity() + actor.set_autopilot(False, CarlaDataProvider.get_traffic_manager_port()) + actor.set_target_velocity(carla.Vector3D(0,0,0)) + actor.set_target_angular_velocity(carla.Vector3D(0,0,0)) + try: + actor.destroy() + except RuntimeError: + pass # Actor was already destroyed + + +class InvadingActorFlow(AtomicBehavior): + """ + Similar to ActorFlow, but this is meant as an actor flow in the opposite direction that invades the lane. + As such, some configurations are different and for clarity, another behavior has been created + + Important parameters: + - source_wp (carla.Waypoint): Waypoint at which actors will be spawned + - sink_wp (carla.Waypoint): Waypoint at which actors will be deleted + - spawn_dist_interval: Distance interval between spawned actors + - sink_dist: Actors closer to the sink than this distance will be deleted + - actors_speed: Speed of the actors part of the flow [m/s] + - offset: offset from the center lane of the actors + """ + + def __init__(self, source_wp, sink_wp, reference_actor, spawn_dist, + sink_dist=2, offset=0, name="OppositeActorFlow"): + """ + Setup class members + """ + super().__init__(name) + self._world = CarlaDataProvider.get_world() + self._tm = CarlaDataProvider.get_client().get_trafficmanager(CarlaDataProvider.get_traffic_manager_port()) + + self._reference_actor = reference_actor + + self._source_wp = source_wp + self._source_transform = self._source_wp.transform + self._source_location = self._source_transform.location + + self._sink_wp = sink_wp + self._sink_location = self._sink_wp.transform.location + + self._spawn_dist = spawn_dist + + self._sink_dist = sink_dist + + self._attribute_filter = {'base_type': 'car', 'has_lights': True, 'special_type': ''} + + self._actor_list = [] + + # Opposite direction needs earlier vehicle detection + self._opt_dict = {'base_vehicle_threshold': 10, 'detection_speed_ratio': 2, 'distance_ratio': 0.2} + self._opt_dict['offset'] = offset + + self._grp = CarlaDataProvider.get_global_route_planner() + self._map = CarlaDataProvider.get_map() + + self._terminated = False + + def initialise(self): + """Get the actor flow source and sink, depending on the reference actor speed""" + self._speed = self._reference_actor.get_speed_limit() # Km / h + self._route = self._grp.trace_route(self._source_location, self._sink_location) + return super().initialise() + + def _spawn_actor(self): + actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', self._source_transform, rolename='scenario', + attribute_filter=self._attribute_filter, tick=False + ) + if actor is None: + return py_trees.common.Status.RUNNING + + controller = BasicAgent(actor, self._speed, self._opt_dict, self._map, self._grp) + controller.set_global_plan(self._route) + self._actor_list.append([actor, controller]) + + def update(self): + """Controls the created actors and creates / removes other when needed""" + # Control the vehicles, removing them when needed + for actor_data in list(self._actor_list): + actor, controller = actor_data + location = CarlaDataProvider.get_location(actor) + if not location: + continue + sink_distance = self._sink_location.distance(location) + if sink_distance < self._sink_dist: + actor.destroy() + self._actor_list.remove(actor_data) + else: + actor.apply_control(controller.run_step()) + + # Spawn new actors if needed + if len(self._actor_list) == 0: + distance = self._spawn_dist + 1 + else: + actor_location = CarlaDataProvider.get_location(self._actor_list[-1][0]) + distance = self._source_location.distance(actor_location) if actor_location else 0 if distance > self._spawn_dist: - actor = CarlaDataProvider.request_new_actor( - 'vehicle.*', self._source_transform, rolename='scenario', safe_blueprint=True, tick=False - ) - if actor is not None: - actor_agent = BasicAgent(actor, 3.6 * self._speed) - actor_agent.set_global_plan(self._route, False) - self._actor_agent_list.append([actor, actor_agent]) + self._spawn_actor() + + return py_trees.common.Status.RUNNING + + def terminate(self, new_status): + """ + Default terminate. Can be extended in derived class + """ + if self._terminated: + return + + self._terminated = True + + for actor, _ in self._actor_list: + # TODO: Actors spawned in the same frame as the behavior termination won't be removed. + # Patched by removing its movement + actor.disable_constant_velocity() + actor.set_autopilot(False, CarlaDataProvider.get_traffic_manager_port()) + actor.set_target_velocity(carla.Vector3D(0,0,0)) + actor.set_target_angular_velocity(carla.Vector3D(0,0,0)) + try: + actor.destroy() + except RuntimeError: + pass # Actor was already destroyed + + +class BicycleFlow(AtomicBehavior): + """ + Behavior that indefinitely creates bicycles at a location, + controls them until another location, and then destroys them. + Therefore, a parallel termination behavior has to be used. + + Important parameters: + - plan (list(carla.Waypoint)): plan used by the bicycles. + - spawn_distance_interval (list(float, float)): Distance between spawned actors + - sink_distance (float): Actors at this distance from the sink will be deleted + - actors_speed (float): Speed of the actors part of the flow [m/s] + - initial_actors (bool): Boolean to initialy populate all the flow with bicycles + """ + + def __init__(self, plan, spawn_dist_interval, sink_dist=2, + actor_speed=20 / 3.6, initial_actors=False, name="BicycleFlow"): + """ + Setup class members + """ + super().__init__(name) + self._rng = CarlaDataProvider.get_random_seed() + + self._plan = plan + self._sink_dist = sink_dist + self._speed = actor_speed + + self._source_transform = self._plan[0][0].transform + self._source_location = self._source_transform.location + self._sink_location = self._plan[-1][0].transform.location + + self._min_spawn_dist = spawn_dist_interval[0] + self._max_spawn_dist = spawn_dist_interval[1] + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + self._initial_actors = initial_actors + + self._opt_dict = {"ignore_traffic_lights": True, "ignore_vehicles": True} + + self._actor_data = [] + self._grp = CarlaDataProvider.get_global_route_planner() + + self._terminated = False + + def initialise(self): + if self._initial_actors: + ref_loc = self._plan[0][0].transform.location + for wp, _ in self._plan: + if wp.is_junction: + continue # Spawning at junctions might break the path, so don't + if wp.transform.location.distance(ref_loc) < self._spawn_dist: + continue + self._spawn_actor(wp.transform) + ref_loc = wp.transform.location self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) - ground_loc = self._world.ground_projection(self._source_transform.location, 2) - if ground_loc.location: - initial_location = ground_loc.location - initial_location.z += 0.06 - actor.set_location(initial_location) + def _spawn_actor(self, transform): + """Spawn the actor""" + # Initial actors don't want all the plan. Remove the points behind them + plan = self._plan + actor_loc = transform.location + while len(plan) > 0: + wp, _ = plan[0] + loc = wp.transform.location + actor_heading = transform.get_forward_vector() + actor_wp_vec = loc - actor_loc + if actor_heading.dot(actor_wp_vec) < 0 or loc.distance(actor_loc) < 10: + plan.pop(0) + else: + break + + if not plan: + return + + actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', transform, rolename='scenario no lights', + attribute_filter={'base_type': 'bicycle'}, tick=False + ) + if actor is None: + return + + controller = BasicAgent(actor, 3.6 * self._speed, opt_dict=self._opt_dict, + map_inst=CarlaDataProvider.get_map(), grp_inst=CarlaDataProvider.get_global_route_planner()) + controller.set_global_plan(plan) + + initial_vec = plan[0][0].transform.get_forward_vector() + actor.set_target_velocity(self._speed * initial_vec) + actor.apply_control(carla.VehicleControl(throttle=1, gear=1, manual_gear_shift=True)) + + self._actor_data.append([actor, controller]) + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + def update(self): + """Controls the created actors and creaes / removes other when needed""" + # Control the vehicles, removing them when needed + for actor_data in list(self._actor_data): + actor, controller = actor_data + location = CarlaDataProvider.get_location(actor) + if not location: + continue + sink_distance = self._sink_location.distance(location) + if sink_distance < self._sink_dist: + actor.destroy() + self._actor_data.remove(actor_data) + else: + actor.apply_control(controller.run_step()) + + # Spawn new actors if needed + if len(self._actor_data) == 0: + distance = self._spawn_dist + 1 + else: + actor_location = CarlaDataProvider.get_location(self._actor_data[-1][0]) + if actor_location is None: + distance = 0 + else: + distance = self._source_location.distance(actor_location) + + if distance > self._spawn_dist: + self._spawn_actor(self._source_transform) return py_trees.common.Status.RUNNING @@ -3038,11 +3915,58 @@ def terminate(self, new_status): """ Default terminate. Can be extended in derived class """ - try: - for actor, _ in self._actor_agent_list: + if self._terminated: + return + + self._terminated = True + + for actor, _ in self._actor_data: + # TODO: Actors spawned in the same frame as the behavior termination won't be removed. + # Patched by removing its movement + actor.disable_constant_velocity() + actor.set_autopilot(False, CarlaDataProvider.get_traffic_manager_port()) + actor.set_target_velocity(carla.Vector3D(0,0,0)) + actor.set_target_angular_velocity(carla.Vector3D(0,0,0)) + try: actor.destroy() - except RuntimeError: - pass # Actor was already destroyed + except RuntimeError: + pass # Actor was already destroyed + + +class OpenVehicleDoor(AtomicBehavior): + + """ + Implementation for a behavior that will open the door of a vehicle, + then close it after a while. + + Important parameters: + - actor: Type of CARLA actors to be spawned + - vehicle_door: The specific door that will be opened + - duration: Duration of the open door + """ + + def __init__(self, actor, vehicle_door, name="OpenVehicleDoor"): + """ + Setup class members + """ + super(OpenVehicleDoor, self).__init__(name, actor) + self._vehicle_door = vehicle_door + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def initialise(self): + """ + Set start time + """ + self._actor.open_door(self._vehicle_door) + super().initialise() + + def update(self): + """ + Keep running until termination condition is satisfied + """ + new_status = py_trees.common.Status.SUCCESS + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status class TrafficLightFreezer(AtomicBehavior): @@ -3446,15 +4370,13 @@ class ScenarioTriggerer(AtomicBehavior): WINDOWS_SIZE = 5 - def __init__(self, actor, route, blackboard_list, distance, - repeat_scenarios=False, debug=False, name="ScenarioTriggerer"): + def __init__(self, actor, route, blackboard_list, distance, debug=False, name="ScenarioTriggerer"): """ Setup class members """ super(ScenarioTriggerer, self).__init__(name) self._world = CarlaDataProvider.get_world() self._map = CarlaDataProvider.get_map() - self._repeat = repeat_scenarios self._debug = debug self._actor = actor @@ -3467,6 +4389,12 @@ def __init__(self, actor, route, blackboard_list, distance, self._route_length = len(self._route) self._waypoints, _ = zip(*self._route) + def add_blackboard(self, blackboard): + """ + Adds new blackboards to the list. Used by the runtime initialization of scenarios + """ + self._blackboard_list.append(blackboard) + def update(self): new_status = py_trees.common.Status.RUNNING @@ -3510,7 +4438,7 @@ def update(self): condition2 = bool(not value) # Already done, if needed - condition3 = bool(self._repeat or black_var_name not in self._triggered_scenarios) + condition3 = bool(black_var_name not in self._triggered_scenarios) if condition1 and condition2 and condition3: _ = blackboard.set(black_var_name, True) @@ -3582,7 +4510,7 @@ def initialise(self): self._start_time = GameTime.get_time() actor_dict[self._actor.id].update_target_speed(self.max_speed, start_time=self._start_time) - self._global_rp = GlobalRoutePlanner(CarlaDataProvider.get_world().get_map(), 1.0) + self._global_rp = CarlaDataProvider.get_global_route_planner() super(KeepLongitudinalGap, self).initialise() @@ -3665,4 +4593,325 @@ def update(self): new_status = py_trees.common.Status.SUCCESS except: # pylint: disable=bare-except print("ActorSource unable to spawn actor") + + +class SwitchWrongDirectionTest(AtomicBehavior): + + """ + Atomic that switch the OutsideRouteLanesTest criterion. + + Args: + active (bool): True: activated; False: deactivated + name (str): name of the behavior + """ + + def __init__(self, active, name="SwitchWrongDirectionTest"): + """ + Setup class members + """ + self._active = active + super().__init__(name) + + def update(self): + py_trees.blackboard.Blackboard().set("AC_SwitchWrongDirectionTest", self._active, overwrite=True) + return py_trees.common.Status.SUCCESS + + +class SwitchMinSpeedCriteria(AtomicBehavior): + + def __init__(self, active, name="ChangeMinSpeed"): + """ + Setup parameters + """ + super().__init__(name) + self._active = active + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def update(self): + """ + keeps track of gap and update the controller accordingly + """ + new_status = py_trees.common.Status.SUCCESS + py_trees.blackboard.Blackboard().set("SwitchMinSpeedCriteria", self._active, overwrite=True) + return new_status + + +class WalkerFlow(AtomicBehavior): + """ + Behavior that indefinitely creates walkers at a location, + controls them until another location, and then destroys them. + Therefore, a parallel termination behavior has to be used. + + There can be more than one target location. + + Important parameters: + - source_location (carla.Location): Location at which actors will be spawned + - sink_locations (list(carla.Location)): Locations at which actors will be deleted + - sink_locations_prob (list(float)): The probability of each sink_location + - spawn_dist_interval (list(float)): Distance between spawned actors + - random_seed : Optional. The seed of numpy's random + - sink_distance: Actors closer to the sink than this distance will be deleted. + Probably due to the navigation module rerouting the walkers, a sink distance of 2 is reasonable. + """ + def __init__(self, source_location, sink_locations, sink_locations_prob, spawn_dist_interval, random_seed=None, sink_dist=2, + name="WalkerFlow"): + """ + Setup class members + """ + super(WalkerFlow, self).__init__(name) + + if random_seed is not None: + self._rng = random.RandomState(random_seed) + else: + self._rng = CarlaDataProvider.get_random_seed() + self._world = CarlaDataProvider.get_world() + + self._controller_bp = self._world.get_blueprint_library().find('controller.ai.walker') + + self._source_location = source_location + + self._sink_locations = sink_locations + self._sink_locations_prob = sink_locations_prob + self._sink_dist = sink_dist + + self._min_spawn_dist = spawn_dist_interval[0] + self._max_spawn_dist = spawn_dist_interval[1] + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + self._batch_size_list = [1,2,3] + + self._walkers = [] + + def update(self): + """Controls the created actors and creates / removes other when needed""" + # Remove walkers when needed + for item in self._walkers: + walker, controller, sink_location = item + loc = CarlaDataProvider.get_location(walker) + if loc.distance(sink_location) < self._sink_dist: + self._destroy_walker(walker, controller) + self._walkers.remove(item) + + # Spawn new walkers + if len(self._walkers) == 0: + distance = self._spawn_dist + 1 + else: + actor_location = CarlaDataProvider.get_location(self._walkers[-1][0]) + distance = self._source_location.distance(actor_location) + + if distance > self._spawn_dist: + # spawn new walkers + walker_amount = self._rng.choice(self._batch_size_list) + for i in range(walker_amount): + spawn_tran = carla.Transform(self._source_location) + spawn_tran.location.y -= i + walker = CarlaDataProvider.request_new_actor( + 'walker.*', spawn_tran, rolename='scenario' + ) + if walker is None: + continue + # Use ai.walker to controll walkers + controller = self._world.try_spawn_actor(self._controller_bp, carla.Transform(), walker) + sink_location = self._rng.choice(a = self._sink_locations, p = self._sink_locations_prob) + controller.start() + controller.go_to_location(sink_location) + # Add to walkers list + self._walkers.append((walker, controller, sink_location)) + + self._spawn_dist = self._rng.uniform(self._min_spawn_dist, self._max_spawn_dist) + + return py_trees.common.Status.RUNNING + + def _destroy_walker(self, walker, controller): + controller.stop() + controller.destroy() + walker.destroy() + + def terminate(self, new_status): + """ + Default terminate. Can be extended in derived class + """ + for walker, controller, _ in self._walkers: + try: + self._destroy_walker(walker, controller) + except RuntimeError: + pass # Actor was already destroyed + +class AIWalkerBehavior(AtomicBehavior): + """ + Behavior that creates a walker controlled by AI Walker controller. + The walker go from source location to sink location. + A parallel termination behavior has to be used. + + Important parameters: + - source_location (carla.Location): Location at which the actor will be spawned + - sink_location (carla.Location): Location at which the actor will be deleted + """ + + def __init__(self, source_location, sink_location, + name="AIWalkerBehavior"): + """ + Setup class members + """ + super(AIWalkerBehavior, self).__init__(name) + + self._world = CarlaDataProvider.get_world() + self._controller_bp = self._world.get_blueprint_library().find('controller.ai.walker') + + self._source_location = source_location + + self._sink_location = sink_location + self._sink_dist = 2 + + self._walker = None + self._controller = None + + def initialise(self): + """ + Spawn the walker at source location. + Setup the AI controller. + + May throw RuntimeError if the walker can not be + spawned at given location. + """ + spawn_tran = carla.Transform(self._source_location) + self._walker = CarlaDataProvider.request_new_actor( + 'walker.*', spawn_tran, rolename='scenario' + ) + if self._walker is None: + raise RuntimeError("Couldn't spawn the walker") + # Use ai.walker to controll the walker + self._controller = self._world.try_spawn_actor( + self._controller_bp, carla.Transform(), self._walker) + self._controller.start() + self._controller.go_to_location(self._sink_location) + + super(AIWalkerBehavior, self).initialise() + + def update(self): + """Controls the created walker""" + # Remove walkers when needed + if self._walker is not None: + loc = CarlaDataProvider.get_location(self._walker) + # At the very beginning of the scenario, the get_location may return None + if loc is not None: + if loc.distance(self._sink_location) < self._sink_dist: + self.terminate(py_trees.common.Status.SUCCESS) + + return py_trees.common.Status.RUNNING + + def _destroy_walker(self, walker, controller): + if controller: + controller.stop() + controller.destroy() + if walker: + walker.destroy() + + def terminate(self, new_status): + """ + Default terminate. Can be extended in derived class + """ + try: + self._destroy_walker(self._walker, self._controller) + except RuntimeError: + pass # Actor was already destroyed + + +class ScenarioTimeout(AtomicBehavior): + + """ + This class is an idle behavior that waits for a set amount of time + before stoping. + + It is meant to be used with the `ScenarioTimeoutTest` to be used at scenarios + that block the ego's route (such as adding obstacles) so that if the ego is + incapable of surpassing them, it isn't blocked forever. Instead, + the scenario will timeout, but it will be penalized by the `ScenarioTimeoutTest` + + Parameters: + - duration: Duration in seconds of this behavior + """ + + def __init__(self, duration, scenario_name, name="ScenarioTimeout"): + """ + Setup actor + """ + super().__init__(name) + self._duration = duration + self._scenario_name = scenario_name + self._start_time = 0 + self._scenario_timeout = False + self._terminated = False + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def initialise(self): + """ + Set start time + """ + self._start_time = GameTime.get_time() + py_trees.blackboard.Blackboard().set("AC_SwitchActorBlockedTest", False, overwrite=True) + super().initialise() + + def update(self): + """ + Keep running until termination condition is satisfied + """ + new_status = py_trees.common.Status.RUNNING + + if GameTime.get_time() - self._start_time > self._duration: + self._scenario_timeout = True + new_status = py_trees.common.Status.SUCCESS + + return new_status + + def terminate(self, new_status): + """ + Modifies the blackboard to tell the `ScenarioTimeoutTest` if the timeout was triggered + """ + if not self._terminated: # py_trees calls the terminate several times for some reason. + py_trees.blackboard.Blackboard().set(f"ScenarioTimeout_{self._scenario_name}", self._scenario_timeout, overwrite=True) + py_trees.blackboard.Blackboard().set("AC_SwitchActorBlockedTest", True, overwrite=True) + self._terminated = True + super().terminate(new_status) + + +class MovePedestrianWithEgo(AtomicBehavior): + + def __init__(self, reference_actor, actor, distance, displacement=0, name="TrackActor"): + """ + Setup actor + """ + super().__init__(name) + self._actor = actor + self._reference_actor = reference_actor + self._distance = distance + self._displacement = displacement + + added_location = carla.Location(x=self._displacement, z=-self._distance) + self._actor.set_location(self._reference_actor.get_location() + added_location) + + self._start_time = 0 + self._teleport_time = 5 + + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def initialise(self): + """ + Set start time + """ + self._start_time = GameTime.get_time() + added_location = carla.Location(x=self._displacement, z=-self._distance) + self._actor.set_location(self._reference_actor.get_location() + added_location) + super().initialise() + + def update(self): + """ + Keep running until termination condition is satisfied + """ + new_status = py_trees.common.Status.RUNNING + + if GameTime.get_time() - self._start_time > self._teleport_time: + added_location = carla.Location(x=self._displacement, z=-self._distance) + self._actor.set_location(self._reference_actor.get_location() + added_location) + self._start_time = GameTime.get_time() return new_status diff --git a/srunner/scenariomanager/scenarioatomics/atomic_criteria.py b/srunner/scenariomanager/scenarioatomics/atomic_criteria.py index 35aa46b85..45dc96115 100644 --- a/srunner/scenariomanager/scenarioatomics/atomic_criteria.py +++ b/srunner/scenariomanager/scenarioatomics/atomic_criteria.py @@ -15,13 +15,13 @@ The atomic criteria are implemented with py_trees. """ -import weakref import math import numpy as np import py_trees import shapely.geometry import carla +from agents.tools.misc import get_speed from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.timer import GameTime @@ -35,34 +35,38 @@ class Criterion(py_trees.behaviour.Behaviour): Important parameters (PUBLIC): - name: Name of the criterion - - expected_value_success: Result in case of success - (e.g. max_speed, zero collisions, ...) - - expected_value_acceptable: Result that does not mean a failure, - but is not good enough for a success - - actual_value: Actual result after running the scenario - - test_status: Used to access the result of the criterion + - actor: Actor of the criterion - optional: Indicates if a criterion is optional (not used for overall analysis) + - terminate on failure: Whether or not the criteria stops on failure + + - test_status: Used to access the result of the criterion + - success_value: Result in case of success (e.g. max_speed, zero collisions, ...) + - acceptable_value: Result that does not mean a failure, but is not good enough for a success + - actual_value: Actual result after running the scenario + - units: units of the 'actual_value'. This is a string and is used by the result writter """ def __init__(self, name, actor, - expected_value_success, - expected_value_acceptable=None, optional=False, terminate_on_failure=False): super(Criterion, self).__init__(name) self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._terminate_on_failure = terminate_on_failure self.name = name self.actor = actor - self.test_status = "INIT" - self.expected_value_success = expected_value_success - self.expected_value_acceptable = expected_value_acceptable - self.actual_value = 0 self.optional = optional - self.list_traffic_events = [] + self._terminate_on_failure = terminate_on_failure + self.test_status = "INIT" # Either "INIT", "RUNNING", "SUCCESS", "ACCEPTABLE" or "FAILURE" + + # Attributes to compare the current state (actual_value), with the expected ones + self.success_value = 0 + self.acceptable_value = None + self.actual_value = 0 + self.units = "times" + + self.events = [] # List of events (i.e collision, sidewalk invasion...) def initialise(self): """ @@ -91,11 +95,12 @@ class MaxVelocityTest(Criterion): - optional [optional]: If True, the result is not considered for an overall pass/fail result """ - def __init__(self, actor, max_velocity_allowed, optional=False, name="CheckMaximumVelocity"): + def __init__(self, actor, max_velocity, optional=False, name="CheckMaximumVelocity"): """ Setup actor and maximum allowed velovity """ - super(MaxVelocityTest, self).__init__(name, actor, max_velocity_allowed, None, optional) + super(MaxVelocityTest, self).__init__(name, actor, optional) + self.success_value = max_velocity def update(self): """ @@ -110,7 +115,7 @@ def update(self): self.actual_value = max(velocity, self.actual_value) - if velocity > self.expected_value_success: + if velocity > self.success_value: self.test_status = "FAILURE" else: self.test_status = "SUCCESS" @@ -124,7 +129,6 @@ def update(self): class DrivenDistanceTest(Criterion): - """ This class contains an atomic test to check the driven distance @@ -137,16 +141,13 @@ class DrivenDistanceTest(Criterion): - optional [optional]: If True, the result is not considered for an overall pass/fail result """ - def __init__(self, - actor, - distance_success, - distance_acceptable=None, - optional=False, - name="CheckDrivenDistance"): + def __init__(self, actor, distance, acceptable_distance=None, optional=False, name="CheckDrivenDistance"): """ Setup actor """ - super(DrivenDistanceTest, self).__init__(name, actor, distance_success, distance_acceptable, optional) + super(DrivenDistanceTest, self).__init__(name, actor, optional) + self.success_value = distance + self.acceptable_value = acceptable_distance self._last_location = None def initialise(self): @@ -174,10 +175,10 @@ def update(self): self.actual_value += location.distance(self._last_location) self._last_location = location - if self.actual_value > self.expected_value_success: + if self.actual_value > self.success_value: self.test_status = "SUCCESS" - elif (self.expected_value_acceptable is not None and - self.actual_value > self.expected_value_acceptable): + elif (self.acceptable_value is not None and + self.actual_value > self.acceptable_value): self.test_status = "ACCEPTABLE" else: self.test_status = "RUNNING" @@ -213,19 +214,14 @@ class AverageVelocityTest(Criterion): - optional [optional]: If True, the result is not considered for an overall pass/fail result """ - def __init__(self, - actor, - avg_velocity_success, - avg_velocity_acceptable=None, - optional=False, + def __init__(self, actor, velocity, acceptable_velocity=None, optional=False, name="CheckAverageVelocity"): """ Setup actor and average velovity expected """ - super(AverageVelocityTest, self).__init__(name, actor, - avg_velocity_success, - avg_velocity_acceptable, - optional) + super(AverageVelocityTest, self).__init__(name, actor, optional) + self.success_value = velocity + self.acceptable_value = acceptable_velocity self._last_location = None self._distance = 0.0 @@ -258,10 +254,10 @@ def update(self): if elapsed_time > 0.0: self.actual_value = self._distance / elapsed_time - if self.actual_value > self.expected_value_success: + if self.actual_value > self.success_value: self.test_status = "SUCCESS" - elif (self.expected_value_acceptable is not None and - self.actual_value > self.expected_value_acceptable): + elif (self.acceptable_value is not None and + self.actual_value > self.acceptable_value): self.test_status = "ACCEPTABLE" else: self.test_status = "RUNNING" @@ -296,28 +292,34 @@ class CollisionTest(Criterion): - optional [optional]: If True, the result is not considered for an overall pass/fail result """ - MIN_AREA_OF_COLLISION = 3 # If closer than this distance, the collision is ignored - MAX_AREA_OF_COLLISION = 5 # If further than this distance, the area is forgotten - MAX_ID_TIME = 5 # Amount of time the last collision if is remembered + COLLISION_RADIUS = 5 # Two collisions that happen within this distance count as one + MAX_ID_TIME = 5 # Two collisions with the same id that happen within this time count as one + EPSILON = 0.1 # Collisions at lower this speed won't be counted as the actor's fault def __init__(self, actor, other_actor=None, other_actor_type=None, - optional=False, name="CollisionTest", terminate_on_failure=False): + optional=False, terminate_on_failure=False, name="CollisionTest"): """ Construction with sensor setup """ - super(CollisionTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure) + super(CollisionTest, self).__init__(name, actor, optional, terminate_on_failure) self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + self._other_actor = other_actor + self._other_actor_type = other_actor_type - world = self.actor.get_world() + # Attributes to store the last collisions's data + self._collision_sensor = None + self._collision_id = None + self._collision_time = None + self._collision_location = None + + def initialise(self): + """ + Creates the sensor and callback""" + world = CarlaDataProvider.get_world() blueprint = world.get_blueprint_library().find('sensor.other.collision') self._collision_sensor = world.spawn_actor(blueprint, carla.Transform(), attach_to=self.actor) - self._collision_sensor.listen(lambda event: self._count_collisions(weakref.ref(self), event)) - - self.other_actor = other_actor - self.other_actor_type = other_actor_type - self.registered_collisions = [] - self.last_id = None - self.collision_time = None + self._collision_sensor.listen(lambda event: self._count_collisions(event)) + super(CollisionTest, self).initialise() def update(self): """ @@ -329,23 +331,16 @@ def update(self): new_status = py_trees.common.Status.FAILURE actor_location = CarlaDataProvider.get_location(self.actor) - new_registered_collisions = [] - - # Loops through all the previous registered collisions - for collision_location in self.registered_collisions: - - # Get the distance to the collision point - distance_vector = actor_location - collision_location - distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2)) - - # If far away from a previous collision, forget it - if distance <= self.MAX_AREA_OF_COLLISION: - new_registered_collisions.append(collision_location) - - self.registered_collisions = new_registered_collisions - if self.last_id and GameTime.get_time() - self.collision_time > self.MAX_ID_TIME: - self.last_id = None + # Check if the last collision can be ignored + if self._collision_location: + distance_vector = actor_location - self._collision_location + if distance_vector.length() > self.COLLISION_RADIUS: + self._collision_location = None + if self._collision_id: + elapsed_time = GameTime.get_time() - self._collision_time + if elapsed_time > self.MAX_ID_TIME: + self._collision_id = None self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) @@ -356,48 +351,46 @@ def terminate(self, new_status): Cleanup sensor """ if self._collision_sensor is not None: + self._collision_sensor.stop() self._collision_sensor.destroy() self._collision_sensor = None - super(CollisionTest, self).terminate(new_status) - @staticmethod - def _count_collisions(weak_self, event): # pylint: disable=too-many-return-statements - """ - Callback to update collision count - """ - self = weak_self() - if not self: - return - + def _count_collisions(self, event): # pylint: disable=too-many-return-statements + """Update collision count""" actor_location = CarlaDataProvider.get_location(self.actor) - # Ignore the current one if it is the same id as before - if self.last_id == event.other_actor.id: + # Check if the care about the other actor + if self._other_actor and self._other_actor.id != event.other_actor.id: return - # Filter to only a specific actor - if self.other_actor and self.other_actor.id != event.other_actor.id: - return - - # Filter to only a specific type - if self.other_actor_type: - if self.other_actor_type == "miscellaneous": - if "traffic" not in event.other_actor.type_id \ - and "static" not in event.other_actor.type_id: + if self._other_actor_type: + if self._other_actor_type == "miscellaneous": # Special OpenScenario case + if "traffic" not in event.other_actor.type_id and "static" not in event.other_actor.type_id: return - else: - if self.other_actor_type not in event.other_actor.type_id: + elif self._other_actor_type not in event.other_actor.type_id: return - # Ignore it if its too close to a previous collision (avoid micro collisions) - for collision_location in self.registered_collisions: + # To avoid multiple counts of the same collision, filter some of them. + if self._collision_id == event.other_actor.id: + return + if self._collision_location: + distance_vector = actor_location - self._collision_location + if distance_vector.length() <= self.COLLISION_RADIUS: + return - distance_vector = actor_location - collision_location - distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2)) + # If the actor speed is 0, the collision isn't its fault + if CarlaDataProvider.get_velocity(self.actor) < self.EPSILON: + return - if distance <= self.MIN_AREA_OF_COLLISION: - return + # The collision is valid, save the data + self.test_status = "FAILURE" + self.actual_value += 1 + + self._collision_time = GameTime.get_time() + self._collision_location = actor_location + if event.other_actor.id != 0: # Number 0: static objects -> ignore it + self._collision_id = event.other_actor.id if ('static' in event.other_actor.type_id or 'traffic' in event.other_actor.type_id) \ and 'sidewalk' not in event.other_actor.type_id: @@ -409,13 +402,8 @@ def _count_collisions(weak_self, event): # pylint: disable=too-many-return-s else: return - collision_event = TrafficEvent(event_type=actor_type) - collision_event.set_dict({ - 'type': event.other_actor.type_id, - 'id': event.other_actor.id, - 'x': actor_location.x, - 'y': actor_location.y, - 'z': actor_location.z}) + collision_event = TrafficEvent(event_type=actor_type, frame=GameTime.get_frame()) + collision_event.set_dict({'other_actor': event.other_actor, 'location': actor_location}) collision_event.set_message( "Agent collided against object with type={} and id={} at (x={}, y={}, z={})".format( event.other_actor.type_id, @@ -423,95 +411,72 @@ def _count_collisions(weak_self, event): # pylint: disable=too-many-return-s round(actor_location.x, 3), round(actor_location.y, 3), round(actor_location.z, 3))) + self.events.append(collision_event) - self.test_status = "FAILURE" - self.actual_value += 1 - self.collision_time = GameTime.get_time() - - self.registered_collisions.append(actor_location) - self.list_traffic_events.append(collision_event) - # Number 0: static objects -> ignore it - if event.other_actor.id != 0: - self.last_id = event.other_actor.id - - -class ActorSpeedAboveThresholdTest(Criterion): +class ActorBlockedTest(Criterion): """ This test will fail if the actor has had its linear velocity lower than a specific value for a specific amount of time Important parameters: - actor: CARLA actor to be used for this test - - speed_threshold: speed required - - below_threshold_max_time: Maximum time (in seconds) the actor can remain under the speed threshold + - min_speed: speed required [m/s] + - max_time: Maximum time (in seconds) the actor can remain under the speed threshold - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test """ - def __init__(self, actor, speed_threshold, below_threshold_max_time, - name="ActorSpeedAboveThresholdTest", terminate_on_failure=False): + def __init__(self, actor, min_speed, max_time, name="ActorBlockedTest", optional=False, terminate_on_failure=False): """ - Class constructor. + Class constructor """ - super(ActorSpeedAboveThresholdTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor - self._speed_threshold = speed_threshold - self._below_threshold_max_time = below_threshold_max_time + super().__init__(name, actor, optional, terminate_on_failure) + self._min_speed = min_speed + self._max_time = max_time self._time_last_valid_state = None + self._active = True + self.units = None # We care about whether or not it fails, no units attached def update(self): """ - Check if the actor speed is above the speed_threshold + Check if the actor speed is above the min_speed """ new_status = py_trees.common.Status.RUNNING - linear_speed = CarlaDataProvider.get_velocity(self._actor) - if linear_speed is not None: - if linear_speed < self._speed_threshold and self._time_last_valid_state: - if (GameTime.get_time() - self._time_last_valid_state) > self._below_threshold_max_time: - # Game over. The actor has been "blocked" for too long - self.test_status = "FAILURE" + # Deactivate/Activate checking by blackboard message + active = py_trees.blackboard.Blackboard().get('AC_SwitchActorBlockedTest') + if active is not None: + self._active = active + self._time_last_valid_state = GameTime.get_time() + py_trees.blackboard.Blackboard().set("AC_SwitchActorBlockedTest", None, overwrite=True) + + if self._active: + linear_speed = CarlaDataProvider.get_velocity(self.actor) + if linear_speed is not None: + if linear_speed < self._min_speed and self._time_last_valid_state: + if (GameTime.get_time() - self._time_last_valid_state) > self._max_time: + # The actor has been "blocked" for too long, save the data + self.test_status = "FAILURE" - # record event - vehicle_location = CarlaDataProvider.get_location(self._actor) - blocked_event = TrafficEvent(event_type=TrafficEventType.VEHICLE_BLOCKED) - ActorSpeedAboveThresholdTest._set_event_message(blocked_event, vehicle_location) - ActorSpeedAboveThresholdTest._set_event_dict(blocked_event, vehicle_location) - self.list_traffic_events.append(blocked_event) - else: - self._time_last_valid_state = GameTime.get_time() + vehicle_location = CarlaDataProvider.get_location(self.actor) + event = TrafficEvent(event_type=TrafficEventType.VEHICLE_BLOCKED, frame=GameTime.get_frame()) + event.set_message('Agent got blocked at (x={}, y={}, z={})'.format( + round(vehicle_location.x, 3), + round(vehicle_location.y, 3), + round(vehicle_location.z, 3)) + ) + event.set_dict({'location': vehicle_location}) + self.events.append(event) + else: + self._time_last_valid_state = GameTime.get_time() if self._terminate_on_failure and (self.test_status == "FAILURE"): new_status = py_trees.common.Status.FAILURE self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) - return new_status - @staticmethod - def _set_event_message(event, location): - """ - Sets the message of the event - """ - - event.set_message('Agent got blocked at (x={}, y={}, z={})'.format(round(location.x, 3), - round(location.y, 3), - round(location.z, 3))) - - @staticmethod - def _set_event_dict(event, location): - """ - Sets the dictionary of the event - """ - event.set_dict({ - 'x': location.x, - 'y': location.y, - 'z': location.z, - }) - class KeepLaneTest(Criterion): - """ This class contains an atomic test for keeping lane. @@ -524,13 +489,12 @@ def __init__(self, actor, optional=False, name="CheckKeepLane"): """ Construction with sensor setup """ - super(KeepLaneTest, self).__init__(name, actor, 0, None, optional) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + super(KeepLaneTest, self).__init__(name, actor, optional) world = self.actor.get_world() blueprint = world.get_blueprint_library().find('sensor.other.lane_invasion') self._lane_sensor = world.spawn_actor(blueprint, carla.Transform(), attach_to=self.actor) - self._lane_sensor.listen(lambda event: self._count_lane_invasion(weakref.ref(self), event)) + self._lane_sensor.listen(lambda event: self._count_lane_invasion(event)) def update(self): """ @@ -556,17 +520,13 @@ def terminate(self, new_status): """ if self._lane_sensor is not None: self._lane_sensor.destroy() - self._lane_sensor = None + self._lane_sensor = None super(KeepLaneTest, self).terminate(new_status) - @staticmethod - def _count_lane_invasion(weak_self, event): + def _count_lane_invasion(self, event): """ Callback to update lane invasion count """ - self = weak_self() - if not self: - return self.actual_value += 1 @@ -586,9 +546,7 @@ def __init__(self, actor, min_x, max_x, min_y, max_y, name="ReachedRegionTest"): Setup trigger region (rectangle provided by [min_x,min_y] and [max_x,max_y] """ - super(ReachedRegionTest, self).__init__(name, actor, 0) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor + super(ReachedRegionTest, self).__init__(name, actor) self._min_x = min_x self._max_x = max_x self._min_y = min_y @@ -600,7 +558,7 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - location = CarlaDataProvider.get_location(self._actor) + location = CarlaDataProvider.get_location(self.actor) if location is None: return new_status @@ -617,7 +575,6 @@ def update(self): new_status = py_trees.common.Status.SUCCESS self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) - return new_status @@ -641,8 +598,7 @@ def __init__(self, actor, duration=0, optional=False, terminate_on_failure=False """ Setup of the variables """ - super(OffRoadTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + super(OffRoadTest, self).__init__(name, actor, optional, terminate_on_failure) self._map = CarlaDataProvider.get_map() self._offroad = False @@ -720,8 +676,7 @@ def __init__(self, actor, duration=0, optional=False, terminate_on_failure=False """ Setup of the variables """ - super(EndofRoadTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + super(EndofRoadTest, self).__init__(name, actor, optional, terminate_on_failure) self._map = CarlaDataProvider.get_map() self._end_of_road = False @@ -767,7 +722,6 @@ def update(self): return py_trees.common.Status.SUCCESS self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) - return new_status @@ -790,15 +744,13 @@ def __init__(self, actor, duration=0, optional=False, terminate_on_failure=False """ Construction with sensor setup """ - super(OnSidewalkTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + super(OnSidewalkTest, self).__init__(name, actor, optional, terminate_on_failure) - self._actor = actor self._map = CarlaDataProvider.get_map() self._onsidewalk_active = False self._outside_lane_active = False - self._actor_location = self._actor.get_location() + self._actor_location = self.actor.get_location() self._wrong_sidewalk_distance = 0 self._wrong_outside_lane_distance = 0 self._sidewalk_start_location = None @@ -824,7 +776,7 @@ def update(self): new_status = py_trees.common.Status.FAILURE # Some of the vehicle parameters - current_tra = CarlaDataProvider.get_transform(self._actor) + current_tra = CarlaDataProvider.get_transform(self.actor) current_loc = current_tra.location current_wp = self._map.get_waypoint(current_loc, lane_type=carla.LaneType.Any) @@ -923,11 +875,11 @@ def update(self): self.test_status = "FAILURE" # Update the distances - distance_vector = CarlaDataProvider.get_location(self._actor) - self._actor_location + distance_vector = CarlaDataProvider.get_location(self.actor) - self._actor_location distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2)) if distance >= 0.02: # Used to avoid micro-changes adding to considerable sums - self._actor_location = CarlaDataProvider.get_location(self._actor) + self._actor_location = CarlaDataProvider.get_location(self.actor) if self._onsidewalk_active: self._wrong_sidewalk_distance += distance @@ -940,7 +892,7 @@ def update(self): self.actual_value += 1 - onsidewalk_event = TrafficEvent(event_type=TrafficEventType.ON_SIDEWALK_INFRACTION) + onsidewalk_event = TrafficEvent(event_type=TrafficEventType.ON_SIDEWALK_INFRACTION, frame=GameTime.get_frame()) self._set_event_message( onsidewalk_event, self._sidewalk_start_location, self._wrong_sidewalk_distance) self._set_event_dict( @@ -948,14 +900,14 @@ def update(self): self._onsidewalk_active = False self._wrong_sidewalk_distance = 0 - self.list_traffic_events.append(onsidewalk_event) + self.events.append(onsidewalk_event) # Register the outside of a lane event if not self._outside_lane_active and self._wrong_outside_lane_distance > 0: self.actual_value += 1 - outsidelane_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_LANE_INFRACTION) + outsidelane_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_LANE_INFRACTION, frame=GameTime.get_frame()) self._set_event_message( outsidelane_event, self._outside_lane_start_location, self._wrong_outside_lane_distance) self._set_event_dict( @@ -963,7 +915,7 @@ def update(self): self._outside_lane_active = False self._wrong_outside_lane_distance = 0 - self.list_traffic_events.append(outsidelane_event) + self.events.append(outsidelane_event) self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) @@ -978,7 +930,7 @@ def terminate(self, new_status): self.actual_value += 1 - onsidewalk_event = TrafficEvent(event_type=TrafficEventType.ON_SIDEWALK_INFRACTION) + onsidewalk_event = TrafficEvent(event_type=TrafficEventType.ON_SIDEWALK_INFRACTION, frame=GameTime.get_frame()) self._set_event_message( onsidewalk_event, self._sidewalk_start_location, self._wrong_sidewalk_distance) self._set_event_dict( @@ -986,14 +938,14 @@ def terminate(self, new_status): self._onsidewalk_active = False self._wrong_sidewalk_distance = 0 - self.list_traffic_events.append(onsidewalk_event) + self.events.append(onsidewalk_event) # If currently outside of our lane, register the event if self._outside_lane_active: self.actual_value += 1 - outsidelane_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_LANE_INFRACTION) + outsidelane_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_LANE_INFRACTION, frame=GameTime.get_frame()) self._set_event_message( outsidelane_event, self._outside_lane_start_location, self._wrong_outside_lane_distance) self._set_event_dict( @@ -1001,7 +953,7 @@ def terminate(self, new_status): self._outside_lane_active = False self._wrong_outside_lane_distance = 0 - self.list_traffic_events.append(outsidelane_event) + self.events.append(outsidelane_event) super(OnSidewalkTest, self).terminate(new_status) @@ -1026,11 +978,7 @@ def _set_event_dict(self, event, location, distance): """ Sets the dictionary of the event """ - event.set_dict({ - 'x': location.x, - 'y': location.y, - 'z': location.z, - 'distance': distance}) + event.set_dict({'location': location, 'distance': distance}) class OutsideRouteLanesTest(Criterion): @@ -1045,26 +993,25 @@ class OutsideRouteLanesTest(Criterion): optional (bool): If True, the result is not considered for an overall pass/fail result """ - ALLOWED_OUT_DISTANCE = 1.3 # At least 0.5, due to the mini-shoulder between lanes and sidewalks - MAX_ALLOWED_VEHICLE_ANGLE = 120.0 # Maximum angle between the yaw and waypoint lane - MAX_ALLOWED_WAYPOINT_ANGLE = 150.0 # Maximum change between the yaw-lane angle between frames - WINDOWS_SIZE = 3 # Amount of additional waypoints checked (in case the first on fails) + ALLOWED_OUT_DISTANCE = 0.5 # At least 0.5, due to the mini-shoulder between lanes and sidewalks + MAX_VEHICLE_ANGLE = 120.0 # Maximum angle between the yaw and waypoint lane + MAX_WAYPOINT_ANGLE = 150.0 # Maximum change between the yaw-lane angle between frames + WINDOWS_SIZE = 3 # Amount of additional waypoints checked (in case the first on fails) def __init__(self, actor, route, optional=False, name="OutsideRouteLanesTest"): """ Constructor """ - super(OutsideRouteLanesTest, self).__init__(name, actor, 0, None, optional) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + super(OutsideRouteLanesTest, self).__init__(name, actor, optional) + self.units = "%" - self._actor = actor self._route = route self._current_index = 0 self._route_length = len(self._route) - self._waypoints, _ = zip(*self._route) + self._route_transforms, _ = zip(*self._route) self._map = CarlaDataProvider.get_map() - self._pre_ego_waypoint = self._map.get_waypoint(self._actor.get_location()) + self._last_ego_waypoint = self._map.get_waypoint(self.actor.get_location()) self._outside_lane_active = False self._wrong_lane_active = False @@ -1072,6 +1019,9 @@ def __init__(self, actor, route, optional=False, name="OutsideRouteLanesTest"): self._last_lane_id = None self._total_distance = 0 self._wrong_distance = 0 + self._wrong_direction_active = True + + self._traffic_event = None def update(self): """ @@ -1084,70 +1034,94 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - if self._terminate_on_failure and (self.test_status == "FAILURE"): - new_status = py_trees.common.Status.FAILURE - # Some of the vehicle parameters - location = CarlaDataProvider.get_location(self._actor) + location = CarlaDataProvider.get_location(self.actor) if location is None: return new_status - # 1) Check if outside route lanes + # Deactivate / activate checking by blackboard message + active = py_trees.blackboard.Blackboard().get('AC_SwitchWrongDirectionTest') + if active is not None: + self._wrong_direction_active = active + py_trees.blackboard.Blackboard().set("AC_SwitchWrongDirectionTest", None, overwrite=True) + self._is_outside_driving_lanes(location) self._is_at_wrong_lane(location) - if self._outside_lane_active or self._wrong_lane_active: + if self._outside_lane_active or (self._wrong_direction_active and self._wrong_lane_active): self.test_status = "FAILURE" - # 2) Get the traveled distance + # Get the traveled distance for index in range(self._current_index + 1, min(self._current_index + self.WINDOWS_SIZE + 1, self._route_length)): # Get the dot product to know if it has passed this location - index_location = self._waypoints[index] - index_waypoint = self._map.get_waypoint(index_location) + route_transform = self._route_transforms[index] + route_location = route_transform.location - wp_dir = index_waypoint.transform.get_forward_vector() # Waypoint's forward vector - wp_veh = location - index_location # vector waypoint - vehicle - dot_ve_wp = wp_veh.x * wp_dir.x + wp_veh.y * wp_dir.y + wp_veh.z * wp_dir.z + wp_dir = route_transform.get_forward_vector() # Waypoint's forward vector + wp_veh = location - route_location # vector waypoint - vehicle - if dot_ve_wp > 0: - # Get the distance traveled - index_location = self._waypoints[index] - current_index_location = self._waypoints[self._current_index] - new_dist = current_index_location.distance(index_location) - - # Add it to the total distance - self._current_index = index + if wp_veh.dot(wp_dir) > 0: + # Get the distance traveled and add it to the total distance + prev_route_location = self._route_transforms[self._current_index].location + new_dist = prev_route_location.distance(route_location) self._total_distance += new_dist # And to the wrong one if outside route lanes - if self._outside_lane_active or self._wrong_lane_active: + if self._outside_lane_active or (self._wrong_direction_active and self._wrong_lane_active): self._wrong_distance += new_dist - self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + if self._wrong_distance: + self._set_traffic_event() + + self._current_index = index + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) return new_status + def _set_traffic_event(self): + """ + Creates the traffic event / updates it + """ + if self._traffic_event is None: + self._traffic_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_ROUTE_LANES_INFRACTION, frame=GameTime.get_frame()) + self.events.append(self._traffic_event) + + percentage = self._wrong_distance / self._total_distance * 100 + self.actual_value = round(percentage, 2) + + self._traffic_event.set_message( + "Agent went outside its route lanes for about {} meters " + "({}% of the completed route)".format( + round(self._wrong_distance, 3), + round(percentage, 2))) + + self._traffic_event.set_dict({ + 'distance': self._wrong_distance, + 'percentage': percentage + }) + + self._traffic_event.set_frame(GameTime.get_frame()) + def _is_outside_driving_lanes(self, location): """ Detects if the ego_vehicle is outside driving lanes """ + driving_wp = self._map.get_waypoint(location, lane_type=carla.LaneType.Driving) + parking_wp = self._map.get_waypoint(location, lane_type=carla.LaneType.Parking) - current_driving_wp = self._map.get_waypoint(location, lane_type=carla.LaneType.Driving, project_to_road=True) - current_parking_wp = self._map.get_waypoint(location, lane_type=carla.LaneType.Parking, project_to_road=True) - - driving_distance = location.distance(current_driving_wp.transform.location) - if current_parking_wp is not None: # Some towns have no parking - parking_distance = location.distance(current_parking_wp.transform.location) + driving_distance = location.distance(driving_wp.transform.location) + if parking_wp is not None: # Some towns have no parking + parking_distance = location.distance(parking_wp.transform.location) else: parking_distance = float('inf') if driving_distance >= parking_distance: distance = parking_distance - lane_width = current_parking_wp.lane_width + lane_width = parking_wp.lane_width else: distance = driving_distance - lane_width = current_driving_wp.lane_width + lane_width = driving_wp.lane_width self._outside_lane_active = bool(distance > (lane_width / 2 + self.ALLOWED_OUT_DISTANCE)) @@ -1155,77 +1129,41 @@ def _is_at_wrong_lane(self, location): """ Detects if the ego_vehicle has invaded a wrong lane """ - - current_waypoint = self._map.get_waypoint(location, lane_type=carla.LaneType.Driving, project_to_road=True) - current_lane_id = current_waypoint.lane_id - current_road_id = current_waypoint.road_id + waypoint = self._map.get_waypoint(location, lane_type=carla.LaneType.Driving) + lane_id = waypoint.lane_id + road_id = waypoint.road_id # Lanes and roads are too chaotic at junctions - if current_waypoint.is_junction: + if waypoint.is_junction: self._wrong_lane_active = False - elif self._last_road_id != current_road_id or self._last_lane_id != current_lane_id: + elif self._last_road_id != road_id or self._last_lane_id != lane_id: - # Route direction can be considered continuous, except after exiting a junction. - if self._pre_ego_waypoint.is_junction: - yaw_waypt = current_waypoint.transform.rotation.yaw % 360 - yaw_actor = self._actor.get_transform().rotation.yaw % 360 + if self._last_ego_waypoint.is_junction: + # Just exited a junction, check the wp direction vs the ego's one + wp_yaw = waypoint.transform.rotation.yaw % 360 + actor_yaw = self.actor.get_transform().rotation.yaw % 360 + angle = (wp_yaw - actor_yaw) % 360 - vehicle_lane_angle = (yaw_waypt - yaw_actor) % 360 - - if vehicle_lane_angle < self.MAX_ALLOWED_VEHICLE_ANGLE \ - or vehicle_lane_angle > (360 - self.MAX_ALLOWED_VEHICLE_ANGLE): + if angle < self.MAX_VEHICLE_ANGLE or angle > (360 - self.MAX_VEHICLE_ANGLE): self._wrong_lane_active = False else: self._wrong_lane_active = True else: - # Check for a big gap in waypoint directions. - yaw_pre_wp = self._pre_ego_waypoint.transform.rotation.yaw % 360 - yaw_cur_wp = current_waypoint.transform.rotation.yaw % 360 - - waypoint_angle = (yaw_pre_wp - yaw_cur_wp) % 360 + # Route direction can be considered continuous, check for a big gap. + last_wp_yaw = self._last_ego_waypoint.transform.rotation.yaw % 360 + wp_yaw = waypoint.transform.rotation.yaw % 360 + angle = (last_wp_yaw - wp_yaw) % 360 - if waypoint_angle >= self.MAX_ALLOWED_WAYPOINT_ANGLE \ - and waypoint_angle <= (360 - self.MAX_ALLOWED_WAYPOINT_ANGLE): # pylint: disable=chained-comparison + if angle > self.MAX_WAYPOINT_ANGLE and angle < (360 - self.MAX_WAYPOINT_ANGLE): # Is the ego vehicle going back to the lane, or going out? Take the opposite self._wrong_lane_active = not bool(self._wrong_lane_active) - else: - - # Changing to a lane with the same direction - self._wrong_lane_active = False # Remember the last state - self._last_lane_id = current_lane_id - self._last_road_id = current_road_id - self._pre_ego_waypoint = current_waypoint - - def terminate(self, new_status): - """ - If there is currently an event running, it is registered - """ - - if self._wrong_distance > 0: - - percentage = self._wrong_distance / self._total_distance * 100 - - outside_lane = TrafficEvent(event_type=TrafficEventType.OUTSIDE_ROUTE_LANES_INFRACTION) - outside_lane.set_message( - "Agent went outside its route lanes for about {} meters " - "({}% of the completed route)".format( - round(self._wrong_distance, 3), - round(percentage, 2))) - - outside_lane.set_dict({ - 'distance': self._wrong_distance, - 'percentage': percentage - }) - - self._wrong_distance = 0 - self.list_traffic_events.append(outside_lane) - self.actual_value = round(percentage, 2) - - super(OutsideRouteLanesTest, self).terminate(new_status) + self._last_lane_id = lane_id + self._last_road_id = road_id + self._last_ego_waypoint = waypoint class WrongLaneTest(Criterion): @@ -1238,24 +1176,23 @@ class WrongLaneTest(Criterion): - optional [optional]: If True, the result is not considered for an overall pass/fail result """ MAX_ALLOWED_ANGLE = 120.0 - MAX_ALLOWED_WAYPOINT_ANGLE = 150.0 + MAX_WAYPOINT_ANGLE = 150.0 def __init__(self, actor, optional=False, name="WrongLaneTest"): """ Construction with sensor setup """ - super(WrongLaneTest, self).__init__(name, actor, 0, None, optional) + super(WrongLaneTest, self).__init__(name, actor, optional) self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor self._map = CarlaDataProvider.get_map() self._last_lane_id = None self._last_road_id = None self._in_lane = True self._wrong_distance = 0 - self._actor_location = self._actor.get_location() - self._previous_lane_waypoint = self._map.get_waypoint(self._actor.get_location()) + self._actor_location = self.actor.get_location() + self._previous_lane_waypoint = self._map.get_waypoint(self.actor.get_location()) self._wrong_lane_start_location = None def update(self): @@ -1268,7 +1205,7 @@ def update(self): if self._terminate_on_failure and (self.test_status == "FAILURE"): new_status = py_trees.common.Status.FAILURE - lane_waypoint = self._map.get_waypoint(self._actor.get_location()) + lane_waypoint = self._map.get_waypoint(self.actor.get_location()) current_lane_id = lane_waypoint.lane_id current_road_id = lane_waypoint.road_id @@ -1291,7 +1228,7 @@ def update(self): math.acos(np.clip(np.dot(p_lane_vector, c_lane_vector) / (np.linalg.norm(p_lane_vector) * np.linalg.norm(c_lane_vector)), -1.0, 1.0))) - if waypoint_angle > self.MAX_ALLOWED_WAYPOINT_ANGLE and self._in_lane: + if waypoint_angle > self.MAX_WAYPOINT_ANGLE and self._in_lane: self.test_status = "FAILURE" self._in_lane = False @@ -1308,8 +1245,8 @@ def update(self): vector_wp = np.array([next_waypoint.transform.location.x - lane_waypoint.transform.location.x, next_waypoint.transform.location.y - lane_waypoint.transform.location.y]) - vector_actor = np.array([math.cos(math.radians(self._actor.get_transform().rotation.yaw)), - math.sin(math.radians(self._actor.get_transform().rotation.yaw))]) + vector_actor = np.array([math.cos(math.radians(self.actor.get_transform().rotation.yaw)), + math.sin(math.radians(self.actor.get_transform().rotation.yaw))]) vehicle_lane_angle = math.degrees( math.acos(np.clip(np.dot(vector_actor, vector_wp) / (np.linalg.norm(vector_wp)), -1.0, 1.0))) @@ -1319,14 +1256,14 @@ def update(self): self.test_status = "FAILURE" self._in_lane = False self.actual_value += 1 - self._wrong_lane_start_location = self._actor.get_location() + self._wrong_lane_start_location = self.actor.get_location() # Keep adding "meters" to the counter - distance_vector = self._actor.get_location() - self._actor_location + distance_vector = self.actor.get_location() - self._actor_location distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2)) if distance >= 0.02: # Used to avoid micro-changes adding add to considerable sums - self._actor_location = CarlaDataProvider.get_location(self._actor) + self._actor_location = CarlaDataProvider.get_location(self.actor) if not self._in_lane and not lane_waypoint.is_junction: self._wrong_distance += distance @@ -1334,13 +1271,13 @@ def update(self): # Register the event if self._in_lane and self._wrong_distance > 0: - wrong_way_event = TrafficEvent(event_type=TrafficEventType.WRONG_WAY_INFRACTION) + wrong_way_event = TrafficEvent(event_type=TrafficEventType.WRONG_WAY_INFRACTION, frame=GameTime.get_frame()) self._set_event_message(wrong_way_event, self._wrong_lane_start_location, self._wrong_distance, current_road_id, current_lane_id) self._set_event_dict(wrong_way_event, self._wrong_lane_start_location, self._wrong_distance, current_road_id, current_lane_id) - self.list_traffic_events.append(wrong_way_event) + self.events.append(wrong_way_event) self._wrong_distance = 0 # Remember the last state @@ -1358,11 +1295,11 @@ def terminate(self, new_status): """ if not self._in_lane: - lane_waypoint = self._map.get_waypoint(self._actor.get_location()) + lane_waypoint = self._map.get_waypoint(self.actor.get_location()) current_lane_id = lane_waypoint.lane_id current_road_id = lane_waypoint.road_id - wrong_way_event = TrafficEvent(event_type=TrafficEventType.WRONG_WAY_INFRACTION) + wrong_way_event = TrafficEvent(event_type=TrafficEventType.WRONG_WAY_INFRACTION, frame=GameTime.get_frame()) self._set_event_message(wrong_way_event, self._wrong_lane_start_location, self._wrong_distance, current_road_id, current_lane_id) self._set_event_dict(wrong_way_event, self._wrong_lane_start_location, @@ -1370,7 +1307,7 @@ def terminate(self, new_status): self._wrong_distance = 0 self._in_lane = True - self.list_traffic_events.append(wrong_way_event) + self.events.append(wrong_way_event) super(WrongLaneTest, self).terminate(new_status) @@ -1394,9 +1331,7 @@ def _set_event_dict(self, event, location, distance, road_id, lane_id): Sets the dictionary of the event """ event.set_dict({ - 'x': location.x, - 'y': location.y, - 'z': location.y, + 'location': location, 'distance': distance, 'road_id': road_id, 'lane_id': lane_id}) @@ -1415,9 +1350,8 @@ class InRadiusRegionTest(Criterion): def __init__(self, actor, x, y, radius, name="InRadiusRegionTest"): """ """ - super(InRadiusRegionTest, self).__init__(name, actor, 0) + super(InRadiusRegionTest, self).__init__(name, actor) self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor self._x = x # pylint: disable=invalid-name self._y = y # pylint: disable=invalid-name self._radius = radius @@ -1428,16 +1362,16 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - location = CarlaDataProvider.get_location(self._actor) + location = CarlaDataProvider.get_location(self.actor) if location is None: return new_status if self.test_status != "SUCCESS": in_radius = math.sqrt(((location.x - self._x)**2) + ((location.y - self._y)**2)) < self._radius if in_radius: - route_completion_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETED) + route_completion_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETED, frame=GameTime.get_frame()) route_completion_event.set_message("Destination was successfully reached") - self.list_traffic_events.append(route_completion_event) + self.events.append(route_completion_event) self.test_status = "SUCCESS" else: self.test_status = "RUNNING" @@ -1466,38 +1400,36 @@ class InRouteTest(Criterion): MAX_ROUTE_PERCENTAGE = 30 # % WINDOWS_SIZE = 5 # Amount of additional waypoints checked - def __init__(self, actor, route, offroad_min=-1, offroad_max=30, name="InRouteTest", terminate_on_failure=False): + def __init__(self, actor, route, offroad_min=None, offroad_max=30, name="InRouteTest", terminate_on_failure=False): """ """ - super(InRouteTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor + super(InRouteTest, self).__init__(name, actor, terminate_on_failure=terminate_on_failure) + self.units = None # We care about whether or not it fails, no units attached + self._route = route self._offroad_max = offroad_max # Unless specified, halve of the max value - if offroad_min == -1: + if offroad_min is None: self._offroad_min = self._offroad_max / 2 else: self._offroad_min = self._offroad_min self._world = CarlaDataProvider.get_world() - self._waypoints, _ = zip(*self._route) + self._route_transforms, _ = zip(*self._route) self._route_length = len(self._route) self._current_index = 0 self._out_route_distance = 0 self._in_safe_route = True self._accum_meters = [] - prev_wp = self._waypoints[0] - for i, wp in enumerate(self._waypoints): - d = wp.distance(prev_wp) - if i > 0: - accum = self._accum_meters[i - 1] - else: - accum = 0 + prev_loc = self._route_transforms[0].location + for i, tran in enumerate(self._route_transforms): + loc = tran.location + d = loc.distance(prev_loc) + accum = 0 if i == 0 else self._accum_meters[i - 1] self._accum_meters.append(d + accum) - prev_wp = wp + prev_loc = loc # Blackboard variable blackv = py_trees.blackboard.Blackboard() @@ -1509,7 +1441,7 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - location = CarlaDataProvider.get_location(self._actor) + location = CarlaDataProvider.get_location(self.actor) if location is None: return new_status @@ -1526,8 +1458,8 @@ def update(self): # Get the closest distance for index in range(self._current_index, min(self._current_index + self.WINDOWS_SIZE + 1, self._route_length)): - ref_waypoint = self._waypoints[index] - distance = math.sqrt(((location.x - ref_waypoint.x) ** 2) + ((location.y - ref_waypoint.y) ** 2)) + ref_location = self._route_transforms[index].location + distance = math.sqrt(((location.x - ref_location.x) ** 2) + ((location.y - ref_location.y) ** 2)) if distance <= shortest_distance: closest_index = index shortest_distance = distance @@ -1559,18 +1491,15 @@ def update(self): blackv = py_trees.blackboard.Blackboard() _ = blackv.set("InRoute", False) - route_deviation_event = TrafficEvent(event_type=TrafficEventType.ROUTE_DEVIATION) + route_deviation_event = TrafficEvent(event_type=TrafficEventType.ROUTE_DEVIATION, frame=GameTime.get_frame()) route_deviation_event.set_message( "Agent deviated from the route at (x={}, y={}, z={})".format( round(location.x, 3), round(location.y, 3), round(location.z, 3))) - route_deviation_event.set_dict({ - 'x': location.x, - 'y': location.y, - 'z': location.z}) + route_deviation_event.set_dict({'location': location}) - self.list_traffic_events.append(route_deviation_event) + self.events.append(route_deviation_event) self.test_status = "FAILURE" self.actual_value += 1 @@ -1591,39 +1520,46 @@ class RouteCompletionTest(Criterion): - route: Route to be checked - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test """ - DISTANCE_THRESHOLD = 10.0 # meters WINDOWS_SIZE = 2 + # Thresholds to return that a route has been completed + DISTANCE_THRESHOLD = 10.0 # meters + PERCENTAGE_THRESHOLD = 99 # % + def __init__(self, actor, route, name="RouteCompletionTest", terminate_on_failure=False): """ """ - super(RouteCompletionTest, self).__init__(name, actor, 100, terminate_on_failure=terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor + super(RouteCompletionTest, self).__init__(name, actor, terminate_on_failure=terminate_on_failure) + self.units = "%" + self.success_value = 100 self._route = route self._map = CarlaDataProvider.get_map() - self._wsize = self.WINDOWS_SIZE - self._current_index = 0 + self._index = 0 self._route_length = len(self._route) - self._waypoints, _ = zip(*self._route) - self.target = self._waypoints[-1] + self._route_transforms, _ = zip(*self._route) + self._route_accum_perc = self._get_acummulated_percentages() - self._accum_meters = [] - prev_wp = self._waypoints[0] - for i, wp in enumerate(self._waypoints): - d = wp.distance(prev_wp) - if i > 0: - accum = self._accum_meters[i - 1] - else: - accum = 0 + self.target_location = self._route_transforms[-1].location - self._accum_meters.append(d + accum) - prev_wp = wp + self._traffic_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETION, frame=0) + self._traffic_event.set_dict({'route_completed': self.actual_value}) + self._traffic_event.set_message("Agent has completed {} of the route".format(self.actual_value)) + self.events.append(self._traffic_event) + + def _get_acummulated_percentages(self): + """Gets the accumulated percentage of each of the route transforms""" + accum_meters = [] + prev_loc = self._route_transforms[0].location + for i, tran in enumerate(self._route_transforms): + d = tran.location.distance(prev_loc) + new_d = 0 if i == 0 else accum_meters[i - 1] + + accum_meters.append(d + new_d) + prev_loc = tran.location - self._traffic_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETION) - self.list_traffic_events.append(self._traffic_event) - self._percentage_route_completed = 0.0 + max_dist = accum_meters[-1] + return [x / max_dist * 100 for x in accum_meters] def update(self): """ @@ -1631,7 +1567,7 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - location = CarlaDataProvider.get_location(self._actor) + location = CarlaDataProvider.get_location(self.actor) if location is None: return new_status @@ -1640,31 +1576,25 @@ def update(self): elif self.test_status in ('RUNNING', 'INIT'): - for index in range(self._current_index, min(self._current_index + self._wsize + 1, self._route_length)): + for index in range(self._index, min(self._index + self.WINDOWS_SIZE + 1, self._route_length)): # Get the dot product to know if it has passed this location - ref_waypoint = self._waypoints[index] - wp = self._map.get_waypoint(ref_waypoint) - wp_dir = wp.transform.get_forward_vector() # Waypoint's forward vector - wp_veh = location - ref_waypoint # vector waypoint - vehicle - dot_ve_wp = wp_veh.x * wp_dir.x + wp_veh.y * wp_dir.y + wp_veh.z * wp_dir.z - - if dot_ve_wp > 0: - # good! segment completed! - self._current_index = index - self._percentage_route_completed = 100.0 * float(self._accum_meters[self._current_index]) \ - / float(self._accum_meters[-1]) - self._traffic_event.set_dict({ - 'route_completed': self._percentage_route_completed}) - self._traffic_event.set_message( - "Agent has completed > {:.2f}% of the route".format( - self._percentage_route_completed)) - - if self._percentage_route_completed > 99.0 and location.distance(self.target) < self.DISTANCE_THRESHOLD: - route_completion_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETED) - route_completion_event.set_message("Destination was successfully reached") - self.list_traffic_events.append(route_completion_event) + route_transform = self._route_transforms[index] + route_location = route_transform.location + wp_dir = route_transform.get_forward_vector() # Waypoint's forward vector + wp_veh = location - route_location # vector route - vehicle + + if wp_veh.dot(wp_dir) > 0: + self._index = index + self.actual_value = self._route_accum_perc[self._index] + + self.actual_value = round(self.actual_value, 2) + self._traffic_event.set_dict({'route_completed': self.actual_value}) + self._traffic_event.set_message("Agent has completed {} of the route".format(self.actual_value)) + + if self.actual_value > self.PERCENTAGE_THRESHOLD \ + and location.distance(self.target_location) < self.DISTANCE_THRESHOLD: self.test_status = "SUCCESS" - self._percentage_route_completed = 100 + self.actual_value = 100 elif self.test_status == "SUCCESS": new_status = py_trees.common.Status.SUCCESS @@ -1677,7 +1607,10 @@ def terminate(self, new_status): """ Set test status to failure if not successful and terminate """ - self.actual_value = round(self._percentage_route_completed, 2) + self.actual_value = round(self.actual_value, 2) + + self._traffic_event.set_dict({'route_completed': self.actual_value}) + self._traffic_event.set_message("Agent has completed {} of the route".format(self.actual_value)) if self.test_status == "INIT": self.test_status = "FAILURE" @@ -1699,17 +1632,14 @@ def __init__(self, actor, name="RunningRedLightTest", terminate_on_failure=False """ Init """ - super(RunningRedLightTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor - self._world = actor.get_world() + super(RunningRedLightTest, self).__init__(name, actor, terminate_on_failure=terminate_on_failure) + self._world = CarlaDataProvider.get_world() self._map = CarlaDataProvider.get_map() self._list_traffic_lights = [] self._last_red_light_id = None - self.actual_value = 0 self.debug = False - all_actors = self._world.get_actors() + all_actors = CarlaDataProvider.get_all_actors() for _actor in all_actors: if 'traffic_light' in _actor.type_id: center, waypoints = self.get_traffic_light_waypoints(_actor) @@ -1732,17 +1662,17 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - transform = CarlaDataProvider.get_transform(self._actor) + transform = CarlaDataProvider.get_transform(self.actor) location = transform.location if location is None: return new_status - veh_extent = self._actor.bounding_box.extent.x + veh_extent = self.actor.bounding_box.extent.x - tail_close_pt = self.rotate_point(carla.Vector3D(-0.8 * veh_extent, 0.0, location.z), transform.rotation.yaw) + tail_close_pt = self.rotate_point(carla.Vector3D(-0.8 * veh_extent, 0, 0), transform.rotation.yaw) tail_close_pt = location + carla.Location(tail_close_pt) - tail_far_pt = self.rotate_point(carla.Vector3D(-veh_extent - 1, 0.0, location.z), transform.rotation.yaw) + tail_far_pt = self.rotate_point(carla.Vector3D(-veh_extent - 1, 0, 0), transform.rotation.yaw) tail_far_pt = location + carla.Location(tail_far_pt) for traffic_light, center, waypoints in self._list_traffic_lights: @@ -1777,20 +1707,19 @@ def update(self): tail_wp = self._map.get_waypoint(tail_far_pt) # Calculate the dot product (Might be unscaled, as only its sign is important) - ve_dir = CarlaDataProvider.get_transform(self._actor).get_forward_vector() + ve_dir = CarlaDataProvider.get_transform(self.actor).get_forward_vector() wp_dir = wp.transform.get_forward_vector() - dot_ve_wp = ve_dir.x * wp_dir.x + ve_dir.y * wp_dir.y + ve_dir.z * wp_dir.z # Check the lane until all the "tail" has passed - if tail_wp.road_id == wp.road_id and tail_wp.lane_id == wp.lane_id and dot_ve_wp > 0: + if tail_wp.road_id == wp.road_id and tail_wp.lane_id == wp.lane_id and ve_dir.dot(wp_dir) > 0: # This light is red and is affecting our lane yaw_wp = wp.transform.rotation.yaw lane_width = wp.lane_width location_wp = wp.transform.location - lft_lane_wp = self.rotate_point(carla.Vector3D(0.4 * lane_width, 0.0, location_wp.z), yaw_wp + 90) + lft_lane_wp = self.rotate_point(carla.Vector3D(0.6 * lane_width, 0, 0), yaw_wp + 90) lft_lane_wp = location_wp + carla.Location(lft_lane_wp) - rgt_lane_wp = self.rotate_point(carla.Vector3D(0.4 * lane_width, 0.0, location_wp.z), yaw_wp - 90) + rgt_lane_wp = self.rotate_point(carla.Vector3D(0.6 * lane_width, 0, 0), yaw_wp - 90) rgt_lane_wp = location_wp + carla.Location(rgt_lane_wp) # Is the vehicle traversing the stop line? @@ -1799,20 +1728,16 @@ def update(self): self.test_status = "FAILURE" self.actual_value += 1 location = traffic_light.get_transform().location - red_light_event = TrafficEvent(event_type=TrafficEventType.TRAFFIC_LIGHT_INFRACTION) + red_light_event = TrafficEvent(event_type=TrafficEventType.TRAFFIC_LIGHT_INFRACTION, frame=GameTime.get_frame()) red_light_event.set_message( "Agent ran a red light {} at (x={}, y={}, z={})".format( traffic_light.id, round(location.x, 3), round(location.y, 3), round(location.z, 3))) - red_light_event.set_dict({ - 'id': traffic_light.id, - 'x': location.x, - 'y': location.y, - 'z': location.z}) + red_light_event.set_dict({'id': traffic_light.id, 'location': location}) - self.list_traffic_events.append(red_light_event) + self.events.append(red_light_event) self._last_red_light_id = traffic_light.id break @@ -1880,43 +1805,33 @@ class RunningStopTest(Criterion): - actor: CARLA actor to be used for this test - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test """ - PROXIMITY_THRESHOLD = 50.0 # meters - SPEED_THRESHOLD = 0.1 - WAYPOINT_STEP = 1.0 # meters + PROXIMITY_THRESHOLD = 4.0 # Stops closer than this distance will be detected [m] + SPEED_THRESHOLD = 0.1 # Minimum speed to consider the actor has stopped [m/s] + WAYPOINT_STEP = 0.5 # m def __init__(self, actor, name="RunningStopTest", terminate_on_failure=False): """ """ - super(RunningStopTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure) - self.logger.debug("%s.__init__()" % (self.__class__.__name__)) - self._actor = actor + super(RunningStopTest, self).__init__(name, actor, terminate_on_failure=terminate_on_failure) self._world = CarlaDataProvider.get_world() self._map = CarlaDataProvider.get_map() self._list_stop_signs = [] self._target_stop_sign = None self._stop_completed = False - self._affected_by_stop = False - self.actual_value = 0 - all_actors = self._world.get_actors() - for _actor in all_actors: + self._last_failed_stop = None + + for _actor in CarlaDataProvider.get_all_actors(): if 'traffic.stop' in _actor.type_id: self._list_stop_signs.append(_actor) - @staticmethod - def point_inside_boundingbox(point, bb_center, bb_extent): - """ - X - :param point: - :param bb_center: - :param bb_extent: - :return: - """ + def point_inside_boundingbox(self, point, bb_center, bb_extent, multiplier=1.2): + """Checks whether or not a point is inside a bounding box.""" # pylint: disable=invalid-name - A = carla.Vector2D(bb_center.x - bb_extent.x, bb_center.y - bb_extent.y) - B = carla.Vector2D(bb_center.x + bb_extent.x, bb_center.y - bb_extent.y) - D = carla.Vector2D(bb_center.x - bb_extent.x, bb_center.y + bb_extent.y) + A = carla.Vector2D(bb_center.x - multiplier * bb_extent.x, bb_center.y - multiplier * bb_extent.y) + B = carla.Vector2D(bb_center.x + multiplier * bb_extent.x, bb_center.y - multiplier * bb_extent.y) + D = carla.Vector2D(bb_center.x - multiplier * bb_extent.x, bb_center.y + multiplier * bb_extent.y) M = carla.Vector2D(point.x, point.y) AB = B - A @@ -1929,58 +1844,66 @@ def point_inside_boundingbox(point, bb_center, bb_extent): return am_ab > 0 and am_ab < ab_ab and am_ad > 0 and am_ad < ad_ad # pylint: disable=chained-comparison - def is_actor_affected_by_stop(self, actor, stop, multi_step=20): + def is_actor_affected_by_stop(self, wp_list, stop): """ - Check if the given actor is affected by the stop + Check if the given actor is affected by the stop. + Without using waypoints, a stop might not be detected if the actor is moving at the lane edge. """ - affected = False - # first we run a fast coarse test - current_location = actor.get_location() - stop_location = stop.get_transform().location - if stop_location.distance(current_location) > self.PROXIMITY_THRESHOLD: - return affected + # Quick distance test + stop_location = stop.get_transform().transform(stop.trigger_volume.location) + actor_location = wp_list[0].transform.location + if stop_location.distance(actor_location) > self.PROXIMITY_THRESHOLD: + return False - stop_t = stop.get_transform() - transformed_tv = stop_t.transform(stop.trigger_volume.location) + # Check if the any of the actor wps is inside the stop's bounding box. + # Using more than one waypoint removes issues with small trigger volumes and backwards movement + stop_extent = stop.trigger_volume.extent + for actor_wp in wp_list: + if self.point_inside_boundingbox(actor_wp.transform.location, stop_location, stop_extent): + return True - # slower and accurate test based on waypoint's horizon and geometric test - list_locations = [current_location] - waypoint = self._map.get_waypoint(current_location) - for _ in range(multi_step): - if waypoint: - next_wps = waypoint.next(self.WAYPOINT_STEP) - if not next_wps: - break - waypoint = next_wps[0] - if not waypoint: - break - list_locations.append(waypoint.transform.location) + return False - for actor_location in list_locations: - if self.point_inside_boundingbox(actor_location, transformed_tv, stop.trigger_volume.extent): - affected = True + def _scan_for_stop_sign(self, actor_transform, wp_list): + """ + Check the stop signs to see if any of them affect the actor. + Ignore all checks when going backwards or through an opposite direction""" - return affected + actor_direction = actor_transform.get_forward_vector() - def _scan_for_stop_sign(self): - target_stop_sign = None + # Ignore all when going backwards + actor_velocity = self.actor.get_velocity() + if actor_velocity.dot(actor_direction) < -0.17: # 100º, just in case + return None - ve_tra = CarlaDataProvider.get_transform(self._actor) - ve_dir = ve_tra.get_forward_vector() + # Ignore all when going in the opposite direction + lane_direction = wp_list[0].transform.get_forward_vector() + if actor_direction.dot(lane_direction) < -0.17: # 100º, just in case + return None - wp = self._map.get_waypoint(ve_tra.location) - wp_dir = wp.transform.get_forward_vector() + for stop in self._list_stop_signs: + if self.is_actor_affected_by_stop(wp_list, stop): + return stop - dot_ve_wp = ve_dir.x * wp_dir.x + ve_dir.y * wp_dir.y + ve_dir.z * wp_dir.z + def _get_waypoints(self, actor): + """Returns a list of waypoints starting from the ego location and a set amount forward""" + wp_list = [] + steps = int(self.PROXIMITY_THRESHOLD / self.WAYPOINT_STEP) - if dot_ve_wp > 0: # Ignore all when going in a wrong lane - for stop_sign in self._list_stop_signs: - if self.is_actor_affected_by_stop(self._actor, stop_sign): - # this stop sign is affecting the vehicle - target_stop_sign = stop_sign - break + # Add the actor location + wp = self._map.get_waypoint(actor.get_location()) + wp_list.append(wp) + + # And its forward waypoints + next_wp = wp + for _ in range(steps): + next_wps = next_wp.next(self.WAYPOINT_STEP) + if not next_wps: + break + next_wp = next_wps[0] + wp_list.append(next_wp) - return target_stop_sign + return wp_list def update(self): """ @@ -1988,54 +1911,40 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING - location = self._actor.get_location() - if location is None: - return new_status + actor_transform = self.actor.get_transform() + check_wps = self._get_waypoints(self.actor) if not self._target_stop_sign: - # scan for stop signs - self._target_stop_sign = self._scan_for_stop_sign() - else: - # we were in the middle of dealing with a stop sign - if not self._stop_completed: - # did the ego-vehicle stop? - current_speed = CarlaDataProvider.get_velocity(self._actor) - if current_speed < self.SPEED_THRESHOLD: - self._stop_completed = True - - if not self._affected_by_stop: - stop_location = self._target_stop_sign.get_location() - stop_extent = self._target_stop_sign.trigger_volume.extent - - if self.point_inside_boundingbox(location, stop_location, stop_extent): - self._affected_by_stop = True - - if not self.is_actor_affected_by_stop(self._actor, self._target_stop_sign): - # is the vehicle out of the influence of this stop sign now? - if not self._stop_completed and self._affected_by_stop: - # did we stop? - self.actual_value += 1 - self.test_status = "FAILURE" - stop_location = self._target_stop_sign.get_transform().location - running_stop_event = TrafficEvent(event_type=TrafficEventType.STOP_INFRACTION) - running_stop_event.set_message( - "Agent ran a stop with id={} at (x={}, y={}, z={})".format( - self._target_stop_sign.id, - round(stop_location.x, 3), - round(stop_location.y, 3), - round(stop_location.z, 3))) - running_stop_event.set_dict({ - 'id': self._target_stop_sign.id, - 'x': stop_location.x, - 'y': stop_location.y, - 'z': stop_location.z}) - - self.list_traffic_events.append(running_stop_event) - - # reset state - self._target_stop_sign = None - self._stop_completed = False - self._affected_by_stop = False + self._target_stop_sign = self._scan_for_stop_sign(actor_transform, check_wps) + return new_status + + if not self._stop_completed: + current_speed = CarlaDataProvider.get_velocity(self.actor) + if current_speed < self.SPEED_THRESHOLD: + self._stop_completed = True + + if not self.is_actor_affected_by_stop(check_wps, self._target_stop_sign): + if not self._stop_completed and self._last_failed_stop != self._target_stop_sign.id: + # did we stop? + self.actual_value += 1 + self.test_status = "FAILURE" + stop_location = self._target_stop_sign.get_transform().location + running_stop_event = TrafficEvent(event_type=TrafficEventType.STOP_INFRACTION, frame=GameTime.get_frame()) + running_stop_event.set_message( + "Agent ran a stop with id={} at (x={}, y={}, z={})".format( + self._target_stop_sign.id, + round(stop_location.x, 3), + round(stop_location.y, 3), + round(stop_location.z, 3))) + running_stop_event.set_dict({'id': self._target_stop_sign.id, 'location': stop_location}) + + self.events.append(running_stop_event) + + self._last_failed_stop = self._target_stop_sign.id + + # Reset state + self._target_stop_sign = None + self._stop_completed = False if self._terminate_on_failure and (self.test_status == "FAILURE"): new_status = py_trees.common.Status.FAILURE @@ -2043,3 +1952,245 @@ def update(self): self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) return new_status + + +class MinimumSpeedRouteTest(Criterion): + + """ + Check at which stage of the route is the actor at each tick + + Important parameters: + - actor: CARLA actor to be used for this test + - route: Route to be checked + - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test + """ + WINDOWS_SIZE = 2 + RATIO = 1 + + def __init__(self, actor, route, checkpoints=1, name="MinimumSpeedRouteTest", terminate_on_failure=False): + """ + """ + super().__init__(name, actor, terminate_on_failure=terminate_on_failure) + self.units = "%" + self.success_value = 100 + self.actual_value = 100 + + self._route = route + self._accum_dist = [] + prev_trans = None + for trans, _ in route: + if prev_trans: + dist = trans.location.distance(prev_trans.location) + self._accum_dist.append(dist + self._accum_dist[-1]) + else: + self._accum_dist.append(0) + prev_trans = trans + self._route_length = len(self._route) + + self._checkpoints = checkpoints + self._checkpoint_dist = self._accum_dist[-1] / self._checkpoints + + self._mean_speed = 0 + self._actor_speed = 0 + self._speed_points = 0 + + self._current_dist = 0 + self._checkpoint_values = [] + + self._index = 0 + + + def update(self): + """ + Check if the actor location is within trigger region + """ + new_status = py_trees.common.Status.RUNNING + + if self._terminate_on_failure and (self.test_status == "FAILURE"): + new_status = py_trees.common.Status.FAILURE + + # Check the actor progress through the route + location = CarlaDataProvider.get_location(self.actor) + if location is None: + return new_status + + for index in range(self._index, min(self._index + self.WINDOWS_SIZE + 1, self._route_length)): + # Get the dot product to know if it has passed this location + route_transform = self._route[index][0] + route_location = route_transform.location + wp_dir = route_transform.get_forward_vector() + wp_veh = location - route_location + + if wp_veh.dot(wp_dir) > 0: + self._index = index + + if self._accum_dist[self._index] - self._current_dist > self._checkpoint_dist: + self._set_traffic_event() + self._current_dist = self._accum_dist[self._index] + self._mean_speed = 0 + self._actor_speed = 0 + self._speed_points = 0 + + # Get the actor speed + velocity = CarlaDataProvider.get_velocity(self.actor) + if velocity is None: + return new_status + + # Get the speed of the surrounding Background Activity + all_vehicles = CarlaDataProvider.get_all_actors().filter('vehicle*') + background_vehicles = [v for v in all_vehicles if v.attributes['role_name'] == 'background'] + + if background_vehicles: + frame_mean_speed = 0 + for vehicle in background_vehicles: + frame_mean_speed += CarlaDataProvider.get_velocity(vehicle) + frame_mean_speed /= len(background_vehicles) + + # Record the data + self._mean_speed += frame_mean_speed + self._actor_speed += velocity + self._speed_points += 1 + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + + return new_status + + def _set_traffic_event(self): + + if self._speed_points > 0 and self._mean_speed: + self._mean_speed /= self._speed_points + self._actor_speed /= self._speed_points + checkpoint_value = round(self._actor_speed / (self.RATIO * self._mean_speed) * 100, 2) + else: + checkpoint_value = 100 + + if checkpoint_value >= self.success_value: + self.test_status = "SUCCESS" + else: + self.test_status = "FAILURE" + + self._traffic_event = TrafficEvent(TrafficEventType.MIN_SPEED_INFRACTION, GameTime.get_frame()) + self._traffic_event.set_dict({'percentage': checkpoint_value}) + self._traffic_event.set_message(f"Average speed is {checkpoint_value}% of the surrounding traffic's one") + self.events.append(self._traffic_event) + + self._checkpoint_values.append(checkpoint_value) + + def terminate(self, new_status): + """ + Set the actual value as a percentage of the two mean speeds, + the test status to failure if not successful and terminate + """ + # Routes end at around 99%, so make sure the last checkpoint is recorded + if self._accum_dist[self._index] / self._accum_dist[-1] > 0.95: + self._set_traffic_event() + + if len(self._checkpoint_values): + self.actual_value = round(sum(self._checkpoint_values) / len(self._checkpoint_values), 2) + super().terminate(new_status) + + +class YieldToEmergencyVehicleTest(Criterion): + + """ + Atomic Criterion to detect if the actor yields its lane to the emergency vehicle behind it. + Detection is done by checking if the ev is in front of the actor + + Args: + actor (carla.Actor): CARLA actor to be used for this test + ev (carla.Actor): The emergency vehicle + optional (bool): If True, the result is not considered for an overall pass/fail result + """ + + WAITING_TIME_THRESHOLD = 15 # Maximum time for actor to block ev + + def __init__(self, actor, ev, optional=False, name="YieldToEmergencyVehicleTest"): + """ + Constructor + """ + self._ev = ev + self._terminated = False + super().__init__(name, actor, optional) + + def update(self): + """ + Monitor that the EV ends up in front of the actor + + returns: + py_trees.common.Status.RUNNING + """ + new_status = py_trees.common.Status.RUNNING + + actor_location = CarlaDataProvider.get_location(self.actor) + if not actor_location: + return new_status + ev_location = CarlaDataProvider.get_location(self._ev) + if not ev_location: + return new_status + + ev_direction = CarlaDataProvider.get_transform(self._ev).get_forward_vector() + actor_ev_vector = actor_location - ev_location + + if ev_direction.dot(actor_ev_vector) > 0: + self.test_status = "FAILURE" + else: + self.test_status = "SUCCESS" + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status + + def terminate(self, new_status): + """Set the traffic event, if needed""" + # Terminates are called multiple times. Do this only once + if not self._terminated: + if self.test_status == "FAILURE": + traffic_event = TrafficEvent(TrafficEventType.YIELD_TO_EMERGENCY_VEHICLE, GameTime.get_frame()) + traffic_event.set_message("Agent failed to yield to an emergency vehicle") + self.events.append(traffic_event) + + self._terminated = True + + super().terminate(new_status) + + +class ScenarioTimeoutTest(Criterion): + + """ + Atomic Criterion to detect if the actor has been incapable of finishing an scenario + + Args: + actor (carla.Actor): CARLA actor to be used for this test + optional (bool): If True, the result is not considered for an overall pass/fail result + """ + + def __init__(self, actor, scenario_name, optional=False, name="ScenarioTimeoutTest"): + """ + Constructor + """ + super().__init__(name, actor, optional) + self.success_value = 0 + self.actual_value = 0 + self._scenario_name = scenario_name + + def update(self): + """wait""" + new_status = py_trees.common.Status.RUNNING + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + return new_status + + def terminate(self, new_status): + """check the blackboard for the data and update the criteria if one found""" + + blackboard_name = f"ScenarioTimeout_{self._scenario_name}" + + timeout = py_trees.blackboard.Blackboard().get(blackboard_name) + if timeout: + self.actual_value = 1 + self.test_status = "FAILURE" + + traffic_event = TrafficEvent(event_type=TrafficEventType.SCENARIO_TIMEOUT, frame=GameTime.get_frame()) + traffic_event.set_message("Agent timed out a scenario") + self.events.append(traffic_event) + py_trees.blackboard.Blackboard().set(blackboard_name, None, True) + + super().terminate(new_status) diff --git a/srunner/scenariomanager/scenarioatomics/atomic_trigger_conditions.py b/srunner/scenariomanager/scenarioatomics/atomic_trigger_conditions.py index 34327ac66..bacb2fac8 100644 --- a/srunner/scenariomanager/scenarioatomics/atomic_trigger_conditions.py +++ b/srunner/scenariomanager/scenarioatomics/atomic_trigger_conditions.py @@ -31,7 +31,7 @@ from srunner.scenariomanager.scenarioatomics.atomic_behaviors import calculate_distance from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.timer import GameTime -from srunner.tools.scenario_helper import get_distance_along_route +from srunner.tools.scenario_helper import get_distance_along_route, get_distance_between_actors import srunner.tools as sr_tools @@ -665,7 +665,7 @@ def __init__(self, reference_actor, actor, distance, comparison_operator=operato self._comparison_operator = comparison_operator if distance_type == "longitudinal": - self._global_rp = GlobalRoutePlanner(CarlaDataProvider.get_world().get_map(), 1.0) + self._global_rp = CarlaDataProvider.get_global_route_planner() else: self._global_rp = None @@ -681,11 +681,8 @@ def update(self): if location is None or reference_location is None: return new_status - distance = sr_tools.scenario_helper.get_distance_between_actors(self._actor, - self._reference_actor, - distance_type=self._distance_type, - freespace=self._freespace, - global_planner=self._global_rp) + distance = get_distance_between_actors( + self._actor, self._reference_actor, self._distance_type, self._freespace, self._global_rp) if self._comparison_operator(distance, self._distance): new_status = py_trees.common.Status.SUCCESS @@ -1124,9 +1121,8 @@ def update(self): # Wait for the vehicle to be in front other_dir = other_next_waypoint.transform.get_forward_vector() act_other_dir = actor_location - other_next_waypoint.transform.location - dot_ve_wp = other_dir.x * act_other_dir.x + other_dir.y * act_other_dir.y + other_dir.z * act_other_dir.z - if dot_ve_wp > 0.0: + if other_dir.dot(act_other_dir) > 0.0: in_front = True # Wait for it to be close-by @@ -1141,6 +1137,57 @@ def update(self): return new_status +class WaitUntilInFrontPosition(AtomicCondition): + + """ + Behavior that support the creation of cut ins. It waits until the actor has passed another actor + + Parameters: + - actor: the one getting in front of the other actor + - transform: the reference transform that the actor will have to get in front of + """ + + def __init__(self, actor, transform, check_distance=True, distance=10, name="WaitUntilInFrontPosition"): + """ + Init + """ + super().__init__(name) + + self._actor = actor + self._ref_transform = transform + self._ref_location = transform.location + self._ref_vector = transform.get_forward_vector() + self._check_distance = check_distance + self._distance = distance + + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + + def update(self): + """ + Checks if the two actors meet the requirements + """ + new_status = py_trees.common.Status.RUNNING + + # Is the actor in front? + location = CarlaDataProvider.get_location(self._actor) + if location is None: + return new_status + + actor_dir = location - self._ref_location + in_front = actor_dir.dot(self._ref_vector) > 0.0 + + # Is the actor close-by? + if not self._check_distance or location.distance(self._ref_location) < self._distance: + close_by = True + else: + close_by = False + + if in_front and close_by: + new_status = py_trees.common.Status.SUCCESS + + return new_status + + class DriveDistance(AtomicCondition): """ @@ -1174,6 +1221,9 @@ def update(self): """ new_status = py_trees.common.Status.RUNNING + if self._location is None: + return new_status + new_location = CarlaDataProvider.get_location(self._actor) self._distance += calculate_distance(self._location, new_location) self._location = new_location @@ -1337,14 +1387,16 @@ class WaitEndIntersection(AtomicCondition): """ Atomic behavior that waits until the vehicles has gone outside the junction. - If currently inside no intersection, it will wait until one is found + If currently inside no intersection, it will wait until one is found. + If 'junction_id' is given, it will wait until that specific junction has finished """ - def __init__(self, actor, debug=False, name="WaitEndIntersection"): + def __init__(self, actor, junction_id=None, debug=False, name="WaitEndIntersection"): super(WaitEndIntersection, self).__init__(name) self.actor = actor self.debug = debug - self.inside_junction = False + self._junction_id = junction_id + self._inside_junction = False self.logger.debug("%s.__init__()" % (self.__class__.__name__)) def update(self): @@ -1354,12 +1406,16 @@ def update(self): location = CarlaDataProvider.get_location(self.actor) waypoint = CarlaDataProvider.get_map().get_waypoint(location) - # Wait for the actor to enter a junction - if not self.inside_junction and waypoint.is_junction: - self.inside_junction = True + # Wait for the actor to enter a / the junction + if not self._inside_junction: + junction = waypoint.get_junction() + if not junction: + return new_status + if not self._junction_id or junction.id == self._junction_id: + self._inside_junction = True # And to leave it - if self.inside_junction and not waypoint.is_junction: + elif self._inside_junction and not waypoint.is_junction: if self.debug: print("--- Leaving the junction") new_status = py_trees.common.Status.SUCCESS diff --git a/srunner/scenariomanager/timer.py b/srunner/scenariomanager/timer.py index ae380b3ea..de5d202bd 100644 --- a/srunner/scenariomanager/timer.py +++ b/srunner/scenariomanager/timer.py @@ -14,6 +14,8 @@ import operator import py_trees +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider + class GameTime(object): @@ -155,3 +157,87 @@ def update(self): self.timeout = True return new_status + + +class RouteTimeoutBehavior(py_trees.behaviour.Behaviour): + """ + Behavior responsible of the route's timeout. With an initial value, + it increases every time the agent advanced through the route, and is dependent on the road's speed. + """ + MIN_TIMEOUT = 300 + TIMEOUT_ROUTE_PERC = 10 + + def __init__(self, ego_vehicle, route, debug=False, name="RouteTimeoutBehavior"): + """ + Setup timeout + """ + super().__init__(name) + self.logger.debug("%s.__init__()" % (self.__class__.__name__)) + self._ego_vehicle = ego_vehicle + self._route = route + self._debug = debug + + self._start_time = None + self._timeout_value = self.MIN_TIMEOUT + self.timeout = False + + # Route variables + self._wsize = 3 + self._current_index = 0 + + self._route_length = len(self._route) + self._route_transforms, _ = zip(*self._route) + + self._route_accum_meters = [] + prev_loc = self._route_transforms[0].location + for i, tran in enumerate(self._route_transforms): + loc = tran.location + d = loc.distance(prev_loc) + accum = 0 if i == 0 else self._route_accum_meters[i - 1] + + self._route_accum_meters.append(d + accum) + prev_loc = loc + + def initialise(self): + """ + Set start_time to current GameTime + """ + self._start_time = GameTime.get_time() + self.logger.debug("%s.initialise()" % (self.__class__.__name__)) + + def update(self): + """ + Get current game time, and compare it to the timeout value + Upon successfully comparison using the provided comparison_operator, + the status changes to SUCCESS + """ + new_status = py_trees.common.Status.RUNNING + + ego_location = CarlaDataProvider.get_location(self._ego_vehicle) + if ego_location is None: + return new_status + + new_index = self._current_index + + for index in range(self._current_index, min(self._current_index + self._wsize + 1, self._route_length)): + route_transform = self._route_transforms[index] + route_veh_vec = ego_location - route_transform.location + if route_veh_vec.dot(route_transform.get_forward_vector()) > 0: + new_index = index + + # Update the timeout value + if new_index > self._current_index: + dist = self._route_accum_meters[new_index] - self._route_accum_meters[self._current_index] + max_speed = self._ego_vehicle.get_speed_limit() / 3.6 + timeout_speed = max_speed * self.TIMEOUT_ROUTE_PERC / 100 + self._timeout_value += dist / timeout_speed + self._current_index = new_index + + elapsed_time = GameTime.get_time() - self._start_time + if elapsed_time > self._timeout_value: + new_status = py_trees.common.Status.SUCCESS + self.timeout = True + + self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status)) + + return new_status diff --git a/srunner/scenariomanager/traffic_events.py b/srunner/scenariomanager/traffic_events.py index 75f5e82bd..d7fe1bda9 100644 --- a/srunner/scenariomanager/traffic_events.py +++ b/srunner/scenariomanager/traffic_events.py @@ -30,6 +30,9 @@ class TrafficEventType(Enum): OUTSIDE_LANE_INFRACTION = 11 OUTSIDE_ROUTE_LANES_INFRACTION = 12 VEHICLE_BLOCKED = 13 + MIN_SPEED_INFRACTION = 14 + YIELD_TO_EMERGENCY_VEHICLE = 15 + SCENARIO_TIMEOUT = 16 class TrafficEvent(object): @@ -38,47 +41,44 @@ class TrafficEvent(object): TrafficEvent definition """ - def __init__(self, event_type, message=None, dictionary=None): + def __init__(self, event_type, frame, message="", dictionary=None): """ Initialize object :param event_type: TrafficEventType defining the type of traffic event + :param frame: frame in which the event happened :param message: optional message to inform users of the event :param dictionary: optional dictionary with arbitrary keys and values """ self._type = event_type + self._frame = frame self._message = message self._dict = dictionary def get_type(self): - """ - @return type - """ + """return the type""" return self._type - def get_message(self): - """ - @return message - """ - if self._message: - return self._message + def get_frame(self): + """return the frame""" + return self._frame - return "" + def set_frame(self, frame): + """return the frame""" + self._frame = frame def set_message(self, message): - """ - Set message - """ + """Set message""" self._message = message - def get_dict(self): - """ - @return dictionary - """ - return self._dict + def get_message(self): + """returns the message""" + return self._message def set_dict(self, dictionary): - """ - Set dictionary - """ + """Set dictionary""" self._dict = dictionary + + def get_dict(self): + """returns the dictionary""" + return self._dict diff --git a/srunner/scenariomanager/weather_sim.py b/srunner/scenariomanager/weather_sim.py index 62362b174..2ad9713ed 100644 --- a/srunner/scenariomanager/weather_sim.py +++ b/srunner/scenariomanager/weather_sim.py @@ -88,7 +88,7 @@ def update(self, delta_time=0): self.carla_weather.sun_azimuth_angle = math.degrees(self._sun.az) -class WeatherBehavior(py_trees.behaviour.Behaviour): +class OSCWeatherBehavior(py_trees.behaviour.Behaviour): """ Atomic to read weather settings from the blackboard and apply these in CARLA. @@ -112,7 +112,7 @@ def __init__(self, name="WeatherBehavior"): """ Setup parameters """ - super(WeatherBehavior, self).__init__(name) + super(OSCWeatherBehavior, self).__init__(name) self._weather = None self._current_time = None @@ -164,3 +164,134 @@ def update(self): py_trees.blackboard.Blackboard().set("Datetime", self._weather.datetime, overwrite=True) return py_trees.common.Status.RUNNING + + +class RouteWeatherBehavior(py_trees.behaviour.Behaviour): + + """ + Given a set of route weathers ([position, carla.WeatherParameters]), + monitors the ego vehicle to dynamically change the weather as the ego advanced through the route. + + This behavior interpolates the desired weather between two weather keypoints and if the extreme cases + (0% and 100%) aren't defined, the closest one will be chosen + (i.e, if the route weather is at 90%, all weathers from 90% to 100% will be the one defined at 90%) + + Use the debug argument to print what is the route's percentage of each route position. + """ + + def __init__(self, ego_vehicle, route, weathers, debug=False, name="RouteWeatherBehavior"): + """ + Setup parameters + """ + super().__init__(name) + self._world = CarlaDataProvider.get_world() + self._ego_vehicle = ego_vehicle + self._route = route + + self._weathers = weathers + if self._weathers[0][0] != 0: # Make sure the weather is defined at 0% + self._weathers.insert(0, [0, self._weathers[0]]) + if self._weathers[-1][0] != 100: # Make sure the weather is defined at 100% + self._weathers.append([100, self._weathers[-1]]) + + self._wsize = 3 + + self._current_index = 0 + self._route_length = len(self._route) + self._route_transforms, _ = zip(*self._route) + self._route_perc = self._get_route_percentages() + if debug: + debug_perc = -1 + for transform, perc in zip(self._route_transforms, self._route_perc): + location = transform.location + new_perc = int(perc) + if new_perc > debug_perc: + self._world.debug.draw_string( + location + carla.Location(z=1), + str(new_perc), + color=carla.Color(50, 50, 50), + life_time=100000 + ) + debug_perc = new_perc + self._route_weathers = self.get_route_weathers() + + def _get_route_percentages(self): + """ + Calculate the accumulated distance percentage at each point in the route + """ + accum_m = [] + prev_loc = self._route_transforms[0].location + for i, tran in enumerate(self._route_transforms): + new_dist = tran.location.distance(prev_loc) + added_dist = 0 if i == 0 else accum_m[i - 1] + accum_m.append(new_dist + added_dist) + prev_loc = tran.location + + max_dist = accum_m[-1] + return [x / max_dist * 100 for x in accum_m] + + def get_route_weathers(self): + """Calculate the desired weather at each point in the route""" + def interpolate(prev_w, next_w, perc, name): + x0 = prev_w[0] + x1 = next_w[0] + if x0 == x1: + raise ValueError("Two weather keypoints have the same route percentage") + y0 = getattr(prev_w[1], name) + y1 = getattr(next_w[1], name) + return y0 + (y1 - y0) * (perc - x0) / (x1 - x0) + + route_weathers = [] + + weather_index = 0 + prev_w = self._weathers[weather_index] + next_w = self._weathers[weather_index + 1] + + for perc in self._route_perc: + if perc > next_w[0]: # Must be strictly less, or an IndexError will occur at 100% + weather_index += 1 + prev_w = self._weathers[weather_index] + next_w = self._weathers[weather_index + 1] + + weather = carla.WeatherParameters() + weather.cloudiness = interpolate(prev_w, next_w, perc, 'cloudiness') + weather.precipitation = interpolate(prev_w, next_w, perc, 'precipitation') + weather.precipitation_deposits = interpolate(prev_w, next_w, perc, 'precipitation_deposits') + weather.wind_intensity = interpolate(prev_w, next_w, perc, 'wind_intensity') + weather.sun_azimuth_angle = interpolate(prev_w, next_w, perc, 'sun_azimuth_angle') + weather.sun_altitude_angle = interpolate(prev_w, next_w, perc, 'sun_altitude_angle') + weather.wetness = interpolate(prev_w, next_w, perc, 'wetness') + weather.fog_distance = interpolate(prev_w, next_w, perc, 'fog_distance') + weather.fog_density = interpolate(prev_w, next_w, perc, 'fog_density') + weather.fog_falloff = interpolate(prev_w, next_w, perc, 'fog_falloff') + weather.scattering_intensity = interpolate(prev_w, next_w, perc, 'scattering_intensity') + weather.mie_scattering_scale = interpolate(prev_w, next_w, perc, 'mie_scattering_scale') + weather.rayleigh_scattering_scale = interpolate(prev_w, next_w, perc, 'rayleigh_scattering_scale') + + route_weathers.append(weather) + + return route_weathers + + def update(self): + """ + Check the location of the ego vehicle, updating the weather if it has advanced through the route + """ + new_status = py_trees.common.Status.RUNNING + + ego_location = CarlaDataProvider.get_location(self._ego_vehicle) + if ego_location is None: + return new_status + + new_index = self._current_index + + for index in range(self._current_index, min(self._current_index + self._wsize + 1, self._route_length)): + route_transform = self._route_transforms[index] + route_veh_vec = ego_location - route_transform.location + if route_veh_vec.dot(route_transform.get_forward_vector()) > 0: + new_index = index + + if new_index > self._current_index: + self._world.set_weather(self._route_weathers[new_index]) + self._current_index = new_index + + return new_status diff --git a/srunner/scenarios/actor_flow.py b/srunner/scenarios/actor_flow.py new file mode 100644 index 000000000..88a461f2c --- /dev/null +++ b/srunner/scenarios/actor_flow.py @@ -0,0 +1,778 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenarios in which another (opposite) vehicle 'illegally' takes +priority, e.g. by running a red traffic light. +""" + +from __future__ import print_function + +import py_trees +import carla + +from agents.navigation.local_planner import RoadOption + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ActorFlow, ScenarioTimeout, WaitForever +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, + WaitEndIntersection, + WaitUntilInFrontPosition) +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import (SwitchRouteSources, + ChangeOppositeBehavior, + HandleJunctionScenario, + RemoveRoadLane) +from srunner.tools.scenario_helper import get_same_dir_lanes, generate_target_waypoint_in_route + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default + +class EnterActorFlow(BasicScenario): + """ + This class holds everything required for a scenario in which another vehicle runs a red light + in front of the ego, forcing it to react. This vehicles are 'special' ones such as police cars, + ambulances or firetrucks. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + ego_location = config.trigger_points[0].location + self._reference_waypoint = CarlaDataProvider.get_map().get_waypoint(ego_location) + + self._sink_distance = 2 + + self._start_actor_flow = convert_dict_to_location(config.other_parameters['start_actor_flow']) + self._end_actor_flow = convert_dict_to_location(config.other_parameters['end_actor_flow']) + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 10) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [20, 50]) + self._scenario_timeout = 240 + + super().__init__("EnterActorFlow", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _create_behavior(self): + """ + Hero vehicle is entering a junction in an urban area, at a signalized intersection, + while another actor runs a red lift, forcing the ego to break. + """ + source_wp = self._map.get_waypoint(self._start_actor_flow) + sink_wp = self._map.get_waypoint(self._end_actor_flow) + + # Get all lanes + source_wps = get_same_dir_lanes(source_wp) + sink_wps = get_same_dir_lanes(sink_wp) + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + + for source_wp, sink_wp in zip(source_wps, sink_wps): + root.add_child(InTriggerDistanceToLocation(self.ego_vehicles[0], sink_wp.transform.location, self._sink_distance)) + root.add_child(ActorFlow( + source_wp, sink_wp, self._source_dist_interval, self._sink_distance, + self._flow_speed, initial_actors=True, initial_junction=True)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + sequence = py_trees.composites.Sequence() + if self.route_mode: + grp = CarlaDataProvider.get_global_route_planner() + route = grp.trace_route(source_wp.transform.location, sink_wp.transform.location) + extra_space = 20 + for i in range(-2, -len(route)-1, -1): + current_wp = route[i][0] + extra_space += current_wp.transform.location.distance(route[i+1][0].transform.location) + if current_wp.is_junction: + break + + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=source_wps, + remove_exits=[], + stop_entries=False, + extend_road_exit=extra_space + )) + sequence.add_child(SwitchRouteSources(False)) + sequence.add_child(root) + if self.route_mode: + sequence.add_child(SwitchRouteSources(True)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class EnterActorFlowV2(EnterActorFlow): + """ + Variation of EnterActorFlow for special highway entry exits with dedicated lanes + """ + def _create_behavior(self): + """ + Hero vehicle is entering a junction in an urban area, at a signalized intersection, + while another actor runs a red lift, forcing the ego to break. + """ + source_wp = self._map.get_waypoint(self._start_actor_flow) + sink_wp = self._map.get_waypoint(self._end_actor_flow) + + # Get all lanes + sink_wps = get_same_dir_lanes(sink_wp) + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(ActorFlow( + source_wp, sink_wp, self._source_dist_interval, self._sink_distance, + self._flow_speed, initial_actors=True, initial_junction=True)) + for sink_wp in sink_wps: + root.add_child(InTriggerDistanceToLocation(self.ego_vehicles[0], sink_wp.transform.location, self._sink_distance)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + exit_wp = generate_target_waypoint_in_route(self._reference_waypoint, self.config.route) + exit_wp = exit_wp.next(10)[0] # just in case the junction maneuvers don't match + + if self.route_mode: + grp = CarlaDataProvider.get_global_route_planner() + route = grp.trace_route(source_wp.transform.location, sink_wp.transform.location) + self._extra_space = 20 + for i in range(-2, -len(route)-1, -1): + current_wp = route[i][0] + self._extra_space += current_wp.transform.location.distance(route[i+1][0].transform.location) + if current_wp.is_junction: + break + + sequence_2 = py_trees.composites.Sequence() + sequence_2.add_child(WaitEndIntersection(self.ego_vehicles[0])) + sequence_2.add_child(HandleJunctionScenario( + clear_junction=False, + clear_ego_entry=False, + remove_entries=[], + remove_exits=[], + stop_entries=False, + extend_road_exit=self._extra_space + )) + sequence_2.add_child(WaitForever()) + root.add_child(sequence_2) + + sequence = py_trees.composites.Sequence() + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=False, + clear_ego_entry=True, + remove_entries=[source_wp], + remove_exits= get_same_dir_lanes(exit_wp), + stop_entries=False, + extend_road_exit=0 + )) + sequence.add_child(SwitchRouteSources(False)) + + sequence.add_child(root) + if self.route_mode: + sequence.add_child(SwitchRouteSources(True)) + + return sequence + + +class HighwayExit(BasicScenario): + """ + This scenario is similar to CrossActorFlow + It will remove the BackgroundActivity from the lane where ActorFlow starts. + Then vehicles (cars) will start driving from start_actor_flow location to end_actor_flow location + in a relatively high speed, forcing the ego to accelerate to cut in the actor flow + then exit from the highway. + This scenario works when Background Activity is running in route mode. And there should be no junctions in front of the ego. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._start_actor_flow = convert_dict_to_location(config.other_parameters['start_actor_flow']) + self._end_actor_flow = convert_dict_to_location(config.other_parameters['end_actor_flow']) + + self._sink_distance = 2 + self._end_distance = 40 + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 10) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [20, 50]) + self._scenario_timeout = 240 + + super().__init__("HighwayExit", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _create_behavior(self): + """ + Vehicles run from the start to the end continuously. + """ + source_wp = self._map.get_waypoint(self._start_actor_flow) + sink_wp = self._map.get_waypoint(self._end_actor_flow) + + grp = CarlaDataProvider.get_global_route_planner() + route = grp.trace_route(source_wp.transform.location, sink_wp.transform.location) + junction_id = None + for wp, _ in route: + if wp.is_junction: + junction_id = wp.get_junction().id + break + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(ActorFlow( + source_wp, sink_wp, self._source_dist_interval, self._sink_distance, + self._flow_speed, initial_actors=True, initial_junction=True)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + root.add_child(WaitEndIntersection(self.ego_vehicles[0], junction_id)) + + sequence = py_trees.composites.Sequence() + + if self.route_mode: + sequence.add_child(RemoveRoadLane(source_wp)) + sequence.add_child(root) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class MergerIntoSlowTraffic(BasicScenario): + """ + This scenario is similar to EnterActorFlow + It will remove the BackgroundActivity from the lane where ActorFlow starts. + Then vehicles (cars) will start driving from start_actor_flow location to end_actor_flow location + in a relatively low speed, ego car must merger into this slow traffic flow. + This scenario works when Background Activity is running in route mode. And applies to a confluence + area at a highway intersection. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + ego_location = config.trigger_points[0].location + self._reference_waypoint = CarlaDataProvider.get_map().get_waypoint(ego_location) + + self._start_actor_flow = convert_dict_to_location(config.other_parameters['start_actor_flow']) + self._end_actor_flow = convert_dict_to_location(config.other_parameters['end_actor_flow']) + self._trigger_point=config.trigger_points[0].location + + self._sink_distance = 2 + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 10) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [20, 50]) + self._scenario_timeout = 240 + + super().__init__("MergerIntoSlowTraffic", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _create_behavior(self): + """ + the ego vehicle mergers into a slow traffic flow from the freeway entrance. + """ + source_wp = self._map.get_waypoint(self._start_actor_flow) + sink_wp = self._map.get_waypoint(self._end_actor_flow) + + # Get all lanes + sink_wps = get_same_dir_lanes(sink_wp) + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + for wp in sink_wps: + root.add_child(InTriggerDistanceToLocation(self.ego_vehicles[0], wp.transform.location, self._sink_distance)) + root.add_child(ActorFlow( + source_wp, sink_wp, self._source_dist_interval, self._sink_distance, + self._flow_speed, initial_actors=True, initial_junction=True)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + sequence = py_trees.composites.Sequence() + if self.route_mode: + + grp = CarlaDataProvider.get_global_route_planner() + route = grp.trace_route(source_wp.transform.location, sink_wp.transform.location) + extra_space = 0 + for i in range(-2, -len(route)-1, -1): + current_wp = route[i][0] + extra_space += current_wp.transform.location.distance(route[i+1][0].transform.location) + if current_wp.is_junction: + break + + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=[source_wp], + remove_exits=[], + stop_entries=False, + extend_road_exit=extra_space + 20 + )) + sequence.add_child(SwitchRouteSources(False)) + sequence.add_child(root) + if self.route_mode: + sequence.add_child(SwitchRouteSources(True)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class MergerIntoSlowTrafficV2(MergerIntoSlowTraffic): + """ + Variation of MergerIntoSlowTraffic + """ + + def _create_behavior(self): + """ + the ego vehicle mergers into a slow traffic flow from the freeway entrance. + """ + source_wp = self._map.get_waypoint(self._start_actor_flow) + sink_wp = self._map.get_waypoint(self._end_actor_flow) + + sink_wps = get_same_dir_lanes(sink_wp) + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(ActorFlow( + source_wp, sink_wp, self._source_dist_interval, self._sink_distance, + self._flow_speed, initial_actors=True, initial_junction=True)) + for sink_wp in sink_wps: + root.add_child(InTriggerDistanceToLocation(self.ego_vehicles[0], sink_wp.transform.location, self._sink_distance)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + exit_wp = generate_target_waypoint_in_route(self._reference_waypoint, self.config.route) + exit_wp = exit_wp.next(10)[0] # just in case the junction maneuvers don't match + + if self.route_mode: + grp = CarlaDataProvider.get_global_route_planner() + route = grp.trace_route(source_wp.transform.location, sink_wp.transform.location) + self._extra_space = 20 + for i in range(-2, -len(route)-1, -1): + current_wp = route[i][0] + self._extra_space += current_wp.transform.location.distance(route[i+1][0].transform.location) + if current_wp.is_junction: + break + + sequence_2 = py_trees.composites.Sequence() + sequence_2.add_child(WaitEndIntersection(self.ego_vehicles[0])) + sequence_2.add_child(HandleJunctionScenario( + clear_junction=False, + clear_ego_entry=False, + remove_entries=[], + remove_exits=[], + stop_entries=False, + extend_road_exit=self._extra_space + )) + sequence_2.add_child(WaitForever()) + root.add_child(sequence_2) + + sequence = py_trees.composites.Sequence() + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=False, + clear_ego_entry=True, + remove_entries=[source_wp], + remove_exits=get_same_dir_lanes(exit_wp), + stop_entries=False, + extend_road_exit=0 + )) + sequence.add_child(SwitchRouteSources(False)) + sequence.add_child(root) + if self.route_mode: + sequence.add_child(SwitchRouteSources(True)) + + return sequence + + +class InterurbanActorFlow(BasicScenario): + """ + Scenario specifically made for the interurban intersections, + where the ego leaves the interurban road by turning left, crossing an actor flow. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._start_actor_flow = convert_dict_to_location(config.other_parameters['start_actor_flow']) + self._end_actor_flow = convert_dict_to_location(config.other_parameters['end_actor_flow']) + + self._sink_distance = 2 + self._end_distance = 40 + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 10) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [20, 50]) + self._scenario_timeout = 240 + + self._reference_wp = self._map.get_waypoint(config.trigger_points[0].location) + + route_entry_wp, route_exit_wp = self._get_entry_exit_route_lanes(self._reference_wp, config.route) + route_exit_wp = route_exit_wp.next(8)[0] # Just in case the junction maneuvers don't match + other_entry_wp = route_exit_wp.get_left_lane() + if not other_entry_wp or other_entry_wp.lane_type != carla.LaneType.Driving: + raise ValueError("Couldn't find an end position") + + self._source_wp = self._map.get_waypoint(self._start_actor_flow) + self._sink_wp = self._map.get_waypoint(self._end_actor_flow) + + self._remove_entries = [route_entry_wp, other_entry_wp, self._source_wp] + + super().__init__("InterurbanActorFlow", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _get_entry_exit_route_lanes(self, wp, route): + + entry_wp = None + exit_wp = None + + # Get the middle entry + dist = float('inf') + index = 0 + for route_index, route_pos in enumerate(route): + route_location = route_pos[0].location + trigger_location = wp.transform.location + + route_dist = trigger_location.distance(route_location) + if route_dist <= dist: + index = route_index + dist = route_dist + + reached_junction = False + for i in range(index, len(route)): + route_transform, road_option = route[i] + + # Enter the junction + if not reached_junction and (road_option in (RoadOption.LEFT, RoadOption.RIGHT, RoadOption.STRAIGHT)): + reached_junction = True + entry_wp = self._map.get_waypoint(route[i-1][0].location) + entry_wp = entry_wp.previous(2)[0] # Just in case + + # End condition for the behavior, at the end of the junction + if reached_junction and (road_option not in (RoadOption.LEFT, RoadOption.RIGHT, RoadOption.STRAIGHT)): + exit_wp = self._map.get_waypoint(route_transform.location) + exit_wp = exit_wp.next(2)[0] # Just in case + break + + return (entry_wp, exit_wp) + + + def _create_behavior(self): + """ + Create an actor flow at the opposite lane which the ego has to cross + """ + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(ActorFlow( + self._source_wp, self._sink_wp, self._source_dist_interval, self._sink_distance, self._flow_speed)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + root.add_child(WaitEndIntersection(self.ego_vehicles[0])) + + sequence = py_trees.composites.Sequence() + + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=False, + clear_ego_entry=True, + remove_entries=self._remove_entries, + remove_exits=[], + stop_entries=False, + extend_road_exit=0 + )) + sequence.add_child(ChangeOppositeBehavior(active=False)) + sequence.add_child(root) + if self.route_mode: + sequence.add_child(ChangeOppositeBehavior(active=True)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class InterurbanAdvancedActorFlow(BasicScenario): + """ + Scenario specifically made for the interurban intersections, + where the ego incorportates into the interurban road by turning left, + first crossing an actor flow, and then merging into another one. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._sink_distance = 2 + + self._reference_wp = self._map.get_waypoint(config.trigger_points[0].location) + self._exit_wp = generate_target_waypoint_in_route(self._reference_wp, config.route) + + self._start_actor_flow_1 = convert_dict_to_location(config.other_parameters['start_actor_flow']) + self._end_actor_flow_1 = convert_dict_to_location(config.other_parameters['end_actor_flow']) + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 10) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [20, 50]) + self._scenario_timeout = 240 + + super().__init__("InterurbanAdvancedActorFlow", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def get_lane_key(self, waypoint): + return str(waypoint.road_id) + '*' + str(waypoint.lane_id) + + def _get_junction_entry_wp(self, entry_wp): + while entry_wp.is_junction: + entry_wps = entry_wp.previous(0.2) + if len(entry_wps) == 0: + return None # Stop when there's no prev + entry_wp = entry_wps[0] + return entry_wp + + def _get_junction_exit_wp(self, exit_wp): + while exit_wp.is_junction: + exit_wps = exit_wp.next(0.2) + if len(exit_wps) == 0: + return None # Stop when there's no prev + exit_wp = exit_wps[0] + return exit_wp + + def _initialize_actors(self, config): + + self._source_wp_1 = self._map.get_waypoint(self._start_actor_flow_1) + self._sink_wp_1 = self._map.get_waypoint(self._end_actor_flow_1) + + self._source_wp_2 = self._sink_wp_1.get_left_lane() + if not self._source_wp_2 or self._source_wp_2.lane_type != carla.LaneType.Driving: + raise ValueError("Couldn't find a position for the actor flow") + self._sink_wp_2 = self._source_wp_1.get_left_lane() + if not self._sink_wp_2 or self._sink_wp_2.lane_type != carla.LaneType.Driving: + raise ValueError("Couldn't find a position for the actor flow") + + if self.route_mode: + grp = CarlaDataProvider.get_global_route_planner() + route = grp.trace_route(self._source_wp_2.transform.location, self._sink_wp_2.transform.location) + self._extra_space = 20 + route_exit_wp = None + for i in range(-2, -len(route)-1, -1): + current_wp = route[i][0] + self._extra_space += current_wp.transform.location.distance(route[i+1][0].transform.location) + if current_wp.is_junction: + junction = current_wp.get_junction() + break + route_exit_wp = current_wp + + route_exit_key = self.get_lane_key(route_exit_wp) + + # Get the route entry waypoint + route_entry_wp = self._reference_wp + while True: + next_wps = route_entry_wp.next(1) + if not next_wps: + break + if next_wps[0].is_junction: + break + route_entry_wp = next_wps[0] + route_entry_key = self.get_lane_key(route_entry_wp) + + entry_wps = [] + entry_keys = [] + exit_wps = [] + exit_keys = [] + + for entry_wp, exit_wp in junction.get_waypoints(carla.LaneType.Driving): + + entry_wp = self._get_junction_entry_wp(entry_wp) + entry_key = self.get_lane_key(entry_wp) + if entry_key != route_entry_key and entry_key not in entry_keys: + entry_wps.append(entry_wp) + entry_keys.append(entry_key) + + exit_wp = self._get_junction_exit_wp(exit_wp) + exit_key = self.get_lane_key(exit_wp) + if exit_key != route_exit_key and exit_key not in exit_keys: + exit_wps.append(exit_wp) + exit_keys.append(exit_key) + + self._remove_entries = entry_wps + self._remove_exits = exit_wps + + def _create_behavior(self): + """ + the ego vehicle mergers into a slow traffic flow from the freeway entrance. + """ + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._sink_wp_2.transform)) + root.add_child(ActorFlow( + self._source_wp_1, self._sink_wp_1, self._source_dist_interval, self._sink_distance, self._flow_speed)) + root.add_child(ActorFlow( + self._source_wp_2, self._sink_wp_2, self._source_dist_interval, self._sink_distance, self._flow_speed)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + sequence = py_trees.composites.Sequence() + if self.route_mode: + + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=self._remove_entries, + remove_exits=self._remove_exits, + stop_entries=False, + extend_road_exit=self._extra_space + )) + sequence.add_child(SwitchRouteSources(False)) + sequence.add_child(ChangeOppositeBehavior(active=False)) + + sequence.add_child(root) + + if self.route_mode: + sequence.add_child(SwitchRouteSources(True)) + sequence.add_child(ChangeOppositeBehavior(active=True)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/background_activity.py b/srunner/scenarios/background_activity.py index 02e4d8461..fdacf5663 100644 --- a/srunner/scenarios/background_activity.py +++ b/srunner/scenarios/background_activity.py @@ -8,120 +8,97 @@ Scenario spawning elements to make the town dynamic and interesting """ -import math from collections import OrderedDict import py_trees -import numpy as np import carla +from agents.navigation.local_planner import RoadOption + from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.scenarioatomics.atomic_behaviors import AtomicBehavior -from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.scenario_helper import get_same_dir_lanes, get_opposite_dir_lanes + +JUNCTION_ENTRY = 'entry' +JUNCTION_MIDDLE = 'middle' +JUNCTION_EXIT = 'exit' +JUNCTION_EXIT_ROAD = 'exit_road' +JUNCTION_EXIT_INACTIVE = 'exit_inactive' + +EGO_JUNCTION = 'junction' +EGO_ROAD = 'road' + +def get_lane_key(waypoint): + """Returns a key corresponding to the waypoint lane. Equivalent to a 'Lane' + object and used to compare waypoint lanes""" + return '' if waypoint is None else get_road_key(waypoint) + '*' + str(waypoint.lane_id) + +def get_road_key(waypoint): + """Returns a key corresponding to the waypoint road. Equivalent to a 'Road' + object and used to compare waypoint roads""" + return '' if waypoint is None else str(waypoint.road_id) + +def is_lane_at_road(lane_key, road_key): + """Returns whether or not a lane is part of a road""" + return lane_key.startswith(road_key) + +def get_lane_key_from_ids(road_id, lane_id): + """Returns the lane corresping to a given road and lane ids""" + return str(road_id) + '*' + str(lane_id) + + +# Debug variables +DEBUG_ROAD = 'road' +DEBUG_OPPOSITE = 'opposite' +DEBUG_JUNCTION = 'junction' +DEBUG_ENTRY = 'entry' +DEBUG_EXIT = 'exit' +DEBUG_CONNECT = 'connect' + +DEBUG_SMALL = 'small' +DEBUG_MEDIUM = 'medium' +DEBUG_LARGE = 'large' DEBUG_COLORS = { - 'road': carla.Color(0, 0, 255), # Blue - 'opposite': carla.Color(255, 0, 0), # Red - 'junction': carla.Color(0, 0, 0), # Black - 'entry': carla.Color(255, 255, 0), # Yellow - 'exit': carla.Color(0, 255, 255), # Teal - 'connect': carla.Color(0, 255, 0), # Green + DEBUG_ROAD: carla.Color(0, 0, 255), # Blue + DEBUG_OPPOSITE: carla.Color(255, 0, 0), # Red + DEBUG_JUNCTION: carla.Color(0, 0, 0), # Black + DEBUG_ENTRY: carla.Color(255, 255, 0), # Yellow + DEBUG_EXIT: carla.Color(0, 255, 255), # Teal + DEBUG_CONNECT: carla.Color(0, 255, 0), # Green } DEBUG_TYPE = { - 'small': [0.8, 0.1], - 'medium': [0.5, 0.15], - 'large': [0.2, 0.2], -} - + DEBUG_SMALL: [0.8, 0.1], + DEBUG_MEDIUM: [0.5, 0.15], + DEBUG_LARGE: [0.2, 0.2], +} # Size, height -def draw_string(world, location, string='', debug_type='road', persistent=False): +def draw_string(world, location, string='', debug_type=DEBUG_ROAD, persistent=False): """Utility function to draw debugging strings""" - v_shift, _ = DEBUG_TYPE.get('small') + v_shift, _ = DEBUG_TYPE.get(DEBUG_SMALL) l_shift = carla.Location(z=v_shift) - color = DEBUG_COLORS.get(debug_type, 'road') - life_time = 0.07 if not persistent else 100000 + color = DEBUG_COLORS.get(debug_type, DEBUG_ROAD) + life_time = 0.06 if not persistent else 100000 world.debug.draw_string(location + l_shift, string, False, color, life_time) - -def draw_point(world, location, point_type='small', debug_type='road', persistent=False): +def draw_point(world, location, point_type=DEBUG_SMALL, debug_type=DEBUG_ROAD, persistent=False): """Utility function to draw debugging points""" - v_shift, size = DEBUG_TYPE.get(point_type, 'small') + v_shift, size = DEBUG_TYPE.get(point_type, DEBUG_SMALL) l_shift = carla.Location(z=v_shift) - color = DEBUG_COLORS.get(debug_type, 'road') - life_time = 0.07 if not persistent else 100000 + color = DEBUG_COLORS.get(debug_type, DEBUG_ROAD) + life_time = 0.06 if not persistent else 100000 world.debug.draw_point(location + l_shift, size, color, life_time) - -def get_same_dir_lanes(waypoint): - """Gets all the lanes with the same direction of the road of a wp""" - same_dir_wps = [waypoint] - - # Check roads on the right - right_wp = waypoint - while True: - possible_right_wp = right_wp.get_right_lane() - if possible_right_wp is None or possible_right_wp.lane_type != carla.LaneType.Driving: - break - right_wp = possible_right_wp - same_dir_wps.append(right_wp) - - # Check roads on the left - left_wp = waypoint - while True: - possible_left_wp = left_wp.get_left_lane() - if possible_left_wp is None or possible_left_wp.lane_type != carla.LaneType.Driving: - break - if possible_left_wp.lane_id * left_wp.lane_id < 0: - break - left_wp = possible_left_wp - same_dir_wps.append(left_wp) - - return same_dir_wps - - -def get_opposite_dir_lanes(waypoint): - """Gets all the lanes with opposite direction of the road of a wp""" - other_dir_wps = [] - other_dir_wp = None - - # Get the first lane of the opposite direction - left_wp = waypoint - while True: - possible_left_wp = left_wp.get_left_lane() - if possible_left_wp is None: - break - if possible_left_wp.lane_id * left_wp.lane_id < 0: - other_dir_wp = possible_left_wp - break - left_wp = possible_left_wp - - if not other_dir_wp: - return other_dir_wps - - # Check roads on the right - right_wp = other_dir_wp - while True: - if right_wp.lane_type == carla.LaneType.Driving: - other_dir_wps.append(right_wp) - possible_right_wp = right_wp.get_right_lane() - if possible_right_wp is None: - break - right_wp = possible_right_wp - - return other_dir_wps - - -def get_lane_key(waypoint): - """Returns a key corresponding to the waypoint lane. Equivalent to a 'Lane' - object and used to compare waypoint lanes""" - return '' if waypoint is None else get_road_key(waypoint) + '*' + str(waypoint.lane_id) - - -def get_road_key(waypoint): - """Returns a key corresponding to the waypoint road. Equivalent to a 'Road' - object and used to compare waypoint roads""" - return '' if waypoint is None else str(waypoint.road_id) +def draw_arrow(world, location1, location2, arrow_type=DEBUG_SMALL, debug_type=DEBUG_ROAD, persistent=False): + """Utility function to draw debugging points""" + if location1 == location2: + draw_point(world, location1, arrow_type, debug_type, persistent) + v_shift, thickness = DEBUG_TYPE.get(arrow_type, DEBUG_SMALL) + l_shift = carla.Location(z=v_shift) + color = DEBUG_COLORS.get(debug_type, DEBUG_ROAD) + life_time = 0.06 if not persistent else 100000 + world.debug.draw_arrow(location1 + l_shift, location2 + l_shift, thickness, thickness, color, life_time) class Source(object): @@ -130,12 +107,10 @@ class Source(object): Source object to store its position and its responsible actors """ - def __init__(self, wp, actors, entry_lane_wp=''): # pylint: disable=invalid-name + def __init__(self, wp, actors, entry_lane_wp='', active=True): # pylint: disable=invalid-name self.wp = wp # pylint: disable=invalid-name self.actors = actors - - # For road sources - self.mapped_key = get_lane_key(wp) + self.active = active # For junction sources self.entry_lane_wp = entry_lane_wp @@ -154,7 +129,8 @@ def __init__(self, junction, junction_id, route_entry_index=None, route_exit_ind self.id = junction_id # pylint: disable=invalid-name self.route_entry_index = route_entry_index self.route_exit_index = route_exit_index - self.exit_road_length = 0 + self.entry_lane_keys = [] + self.exit_lane_keys = [] self.route_entry_keys = [] self.route_exit_keys = [] self.opposite_entry_keys = [] @@ -166,81 +142,32 @@ def __init__(self, junction, junction_id, route_entry_index=None, route_exit_ind # State self.entry_sources = [] - self.exit_sources = [] self.exit_dict = OrderedDict() self.actor_dict = OrderedDict() - self.scenario_info = { - 'direction': None, - 'remove_entries': False, - 'remove_middle': False, - 'remove_exits': False, - } - def contains(self, other_junction): - """Checks whether or not a carla.Junction is part of the class""" - other_id = other_junction.id + # Junction scenario variables + self.stop_non_route_entries = False + self.clear_middle = False + self.inactive_entry_keys = [] + self.inactive_exit_keys = [] + + def contains_wp(self, wp): + """Checks whether or not a carla.Waypoint is inside the junction""" + if not wp.is_junction: + return False + other_id = wp.get_junction().id for junction in self.junctions: if other_id == junction.id: return True return False -class BackgroundActivity(BasicScenario): - - """ - Implementation of a scenario to spawn a set of background actors, - and to remove traffic jams in background traffic - - This is a single ego vehicle scenario - """ - - def __init__(self, world, ego_vehicle, config, route, night_mode=False, debug_mode=False, timeout=0): - """ - Setup all relevant parameters and create scenario - """ - self._map = CarlaDataProvider.get_map() - self.ego_vehicle = ego_vehicle - self.route = route - self.config = config - self._night_mode = night_mode - self.debug = debug_mode - self.timeout = timeout # Timeout of scenario in seconds - - super(BackgroundActivity, self).__init__("BackgroundActivity", - [ego_vehicle], - config, - world, - debug_mode, - terminate_on_failure=True, - criteria_enable=True) - - def _create_behavior(self): - """ - Basic behavior do nothing, i.e. Idle - """ - # Check if a vehicle is further than X, destroy it if necessary and respawn it - return BackgroundBehavior(self.ego_vehicle, self.route, self._night_mode) - - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - pass - - def __del__(self): - """ - Remove all actors upon deletion - """ - pass - - class BackgroundBehavior(AtomicBehavior): """ Handles the background activity """ - def __init__(self, ego_actor, route, night_mode=False, debug=False, name="BackgroundBehavior"): + def __init__(self, ego_actor, route, debug=False, name="BackgroundBehavior"): """ Setup class members """ @@ -248,17 +175,23 @@ def __init__(self, ego_actor, route, night_mode=False, debug=False, name="Backgr self.debug = debug self._map = CarlaDataProvider.get_map() self._world = CarlaDataProvider.get_world() - timestep = self._world.get_snapshot().timestamp.delta_seconds - self._tm = CarlaDataProvider.get_client().get_trafficmanager( - CarlaDataProvider.get_traffic_manager_port()) + self._tm_port = CarlaDataProvider.get_traffic_manager_port() + self._tm = CarlaDataProvider.get_client().get_trafficmanager(self._tm_port) self._tm.global_percentage_speed_difference(0.0) - self._night_mode = night_mode + self._rng = CarlaDataProvider.get_random_seed() + + self._attribute_filter = {'base_type': 'car', 'special_type': '', 'has_lights': True, } # Global variables self._ego_actor = ego_actor - self._ego_state = 'road' + self._ego_state = EGO_ROAD + self._ego_wp = None + self._ego_key = "" self._route_index = 0 self._get_route_data(route) + self._actors_speed_perc = {} # Dictionary actor - percentage + self._all_actors = [] + self._lane_width_threshold = 2.25 # Used to stop some behaviors at narrow lanes to avoid problems [m] self._spawn_vertical_shift = 0.2 self._reuse_dist = 10 # When spawning actors, might reuse actors closer to this distance @@ -266,81 +199,75 @@ def __init__(self, ego_actor, route, night_mode=False, debug=False, name="Backgr self._fake_junction_ids = [] self._fake_lane_pair_keys = [] + # Initialisation values + self._vehicle_lane_change = False + self._vehicle_lights = True + self._vehicle_leading_distance = 10 + self._vehicle_offset = 0.1 + # Road variables - self._road_actors = [] - self._road_back_actors = {} # Dictionary mapping the actors behind the ego to their lane - self._road_ego_key = None - self._road_extra_front_actors = 0 - self._road_sources = [] + self._road_dict = {} # Dictionary lane key -> actor source self._road_checker_index = 0 - self._road_ego_key = "" - - self._road_front_vehicles = 3 # Amount of vehicles in front of the ego - self._road_back_vehicles = 3 # Amount of vehicles behind the ego - self._road_vehicle_dist = 8 # Distance road vehicles leave betweeen each other[m] - self._road_spawn_dist = 11 # Initial distance between spawned road vehicles [m] - self._road_new_sources_dist = 20 # Distance of the source to the start of the new lanes - self._radius_increase_ratio = 1.8 # Meters the radius increases per m/s of the ego - self._extra_radius = 0.0 # Extra distance to avoid the road behavior from blocking - self._extra_radius_increase_ratio = 0.5 * timestep # Distance the radius increases per tick (0.5 m/s) - self._max_extra_radius = 10 # Max extra distance + + self._road_front_vehicles = 2 # Amount of vehicles in front of the ego + self._road_back_vehicles = 2 # Amount of vehicles behind the ego + self._radius_increase_ratio = 1.7 # Meters the radius increases per m/s of the ego + + self._base_junction_detection = 30 + self._detection_ratio = 1.5 # Meters the radius increases per m/s of the ego + + self._road_extra_front_actors = 0 # For cases where we want more space but not more vehicles + self._road_spawn_dist = 15 # Distance between spawned vehicles [m] + self._road_extra_space = 0 # Extra space for the road vehicles + + self._active_road_sources = True self._base_min_radius = 0 self._base_max_radius = 0 self._min_radius = 0 self._max_radius = 0 - self._junction_detection_dist = 0 + self._detection_dist = 0 self._get_road_radius() # Junction variables - self._junctions = [] - self._active_junctions = [] + self._junctions = [] # List with all the junctions part of the route, in order of appearance + self._active_junctions = [] # List of all the active junctions self._junction_sources_dist = 40 # Distance from the entry sources to the junction [m] - self._junction_vehicle_dist = 8 # Distance junction vehicles leave betweeen each other[m] - self._junction_spawn_dist = 10 # Initial distance between spawned junction vehicles [m] - self._junction_sources_max_actors = 5 # Maximum vehicles alive at the same time per source + self._junction_sources_max_actors = 6 # Maximum vehicles alive at the same time per source + self._junction_spawn_dist = 15 # Distance between spawned vehicles [m] + self._junction_minimum_source_dist = 15 # Minimum distance between sources and their junction + + self._junction_source_perc = 80 # Probability [%] of the source being created # Opposite lane variables self._opposite_actors = [] self._opposite_sources = [] self._opposite_route_index = 0 - self._opposite_removal_dist = 30 # Distance at which actors are destroyed - self._opposite_sources_dist = 60 # Distance from the ego to the opposite sources [m] - self._opposite_vehicle_dist = 10 # Distance opposite vehicles leave betweeen each other[m] - self._opposite_spawn_dist = 20 # Initial distance between spawned opposite vehicles [m] - self._opposite_sources_max_actors = 8 # Maximum vehicles alive at the same time per source - - # Scenario 2 variables - self._is_scenario_2_active = False - self._scenario_2_actors = [] - self._activate_break_scenario = False - self._break_duration = 7 # Duration of the scenario - self._next_scenario_time = float('inf') - - # Scenario 4 variables - self._is_scenario_4_active = False - self._scenario_4_actors = [] - self._ego_exitted_junction = False - self._crossing_dist = None # Distance between the crossing object and the junction exit - self._start_ego_wp = None - - # Junction scenario variables - self.scenario_info = { - 'direction': None, - 'remove_entries': False, - 'remove_middle': False, - 'remove_exits': False, - } # Same as the Junction.scenario_info, but this stores the data in case no junctions are active + self._opposite_spawn_dist = 40 # Distance between spawned vehicles [m] + self._opposite_sources_dist = 80 # Distance from the ego to the opposite sources [m]. Twice the spawn distance + + self._active_opposite_sources = True # Flag to (de)activate all opposite sources + + # Scenario variables: + self._scenario_stopped_actors = [] # Actors stopped by a hard break scenario + self._scenario_stopped_back_actors = [] # Actors stopped by a open doors scenario + self._scenario_max_speed = 0 # Max speed of the Background Activity. Deactivated with a value of 0 + self._scenario_junction_entry = False # Flag indicating the ego is entering a junction + self._scenario_junction_entry_distance = self._road_spawn_dist # Min distance between vehicles and ego + self._scenario_removed_lane = False # Flag indicating a scenario has removed a lane + self._scenario_remove_lane_offset = 0 def _get_route_data(self, route): """Extract the information from the route""" self._route = [] # Transform the route into a list of waypoints + self._route_options = [] # Extract the RoadOptions from the route self._accum_dist = [] # Save the total traveled distance for each waypoint prev_trans = None - for trans, _ in route: + for trans, option in route: self._route.append(self._map.get_waypoint(trans.location)) + self._route_options.append(option) if prev_trans: dist = trans.location.distance(prev_trans.location) self._accum_dist.append(dist + self._accum_dist[-1]) @@ -359,82 +286,72 @@ def _get_road_radius(self): stopped. Between the two, the velocity decreases linearly""" self._base_min_radius = (self._road_front_vehicles + self._road_extra_front_actors) * self._road_spawn_dist self._base_max_radius = (self._road_front_vehicles + self._road_extra_front_actors + 1) * self._road_spawn_dist - self._min_radius = self._base_min_radius - self._max_radius = self._base_max_radius + self._min_radius = self._base_min_radius + self._road_extra_space + self._max_radius = self._base_max_radius + self._road_extra_space def initialise(self): """Creates the background activity actors. Pressuposes that the ego is at a road""" self._create_junction_dict() ego_wp = self._route[0] - self._road_ego_key = get_lane_key(ego_wp) same_dir_wps = get_same_dir_lanes(ego_wp) - self._initialise_road_behavior(same_dir_wps, ego_wp) + + self._initialise_road_behavior(same_dir_wps) self._initialise_opposite_sources() self._initialise_road_checker() def update(self): - new_status = py_trees.common.Status.RUNNING - prev_ego_index = self._route_index + # Check if the TM destroyed an actor - if self._route_index > 0: + if self._route_index > 0: # TODO: This check is due to intialization problem self._check_background_actors() - # Get ego's odometry. For robustness, the closest route point will be used - location = CarlaDataProvider.get_location(self._ego_actor) - ego_wp = self._update_ego_route_location(location) - ego_transform = ego_wp.transform - if self.debug: - string = 'EGO_' + self._ego_state[0].upper() - draw_string(self._world, location, string, self._ego_state, False) + # Update ego's route position. For robustness, the route point is used for most calculus + self._update_ego_data() # Parameters and scenarios self._update_parameters() - self._manage_break_scenario() # Update ego state - if self._ego_state == 'junction': - self._monitor_ego_junction_exit(ego_wp) - self._monitor_nearby_junctions() + if self._ego_state == EGO_JUNCTION: + self._monitor_ego_junction_exit() + self._monitor_incoming_junctions() # Update_actors - if self._ego_state == 'junction': - self._monitor_ego_junction_exit(ego_wp) + if self._ego_state == EGO_JUNCTION: self._update_junction_actors() self._update_junction_sources() else: - self._update_road_actors(prev_ego_index, self._route_index) - self._move_road_checker(prev_ego_index, self._route_index) - self._move_opposite_sources(prev_ego_index, self._route_index) + self._update_road_actors() + + self._move_road_sources(prev_ego_index) + self._update_road_sources() + + self._monitor_topology_changes(prev_ego_index) + self._monitor_road_changes(prev_ego_index) + + self._move_opposite_sources(prev_ego_index) self._update_opposite_sources() # Update non junction sources - self._update_opposite_actors(ego_transform) - self._update_road_sources(ego_transform.location) + self._update_opposite_actors() - self._monitor_scenario_4_end(ego_transform.location) + # Update the speed of all vehicles + self._set_actors_speed() - return new_status + return py_trees.common.Status.RUNNING def terminate(self, new_status): """Destroy all actors""" - all_actors = self._get_actors() + all_actors = list(self._actors_speed_perc) for actor in list(all_actors): self._destroy_actor(actor) super(BackgroundBehavior, self).terminate(new_status) - def _get_actors(self): - """Returns a list of all actors part of the background activity""" - actors = self._road_actors + self._opposite_actors - for junction in self._active_junctions: - actors.extend(list(junction.actor_dict)) - return actors - def _check_background_actors(self): """Checks if the Traffic Manager has removed a backgroudn actor""" - background_actors = self._get_actors() - alive_ids = [actor.id for actor in self._world.get_actors().filter('vehicle*')] - for actor in background_actors: + alive_ids = [actor.id for actor in CarlaDataProvider.get_all_actors().filter('vehicle*')] + for actor in list(self._all_actors): if actor.id not in alive_ids: self._remove_actor_info(actor) @@ -478,7 +395,6 @@ def _get_junctions_data(self): if prev_junction: start_dist = self._accum_dist[i] prev_end_dist = self._accum_dist[prev_junction.route_exit_index] - prev_junction.exit_road_length = start_dist - prev_end_dist # Same junction as the prev one and closer than 2 meters if prev_junction and prev_junction.junctions[-1].id == junction_id: @@ -493,59 +409,83 @@ def _get_junctions_data(self): junction_data.append(Junction(next_wp.get_junction(), junction_num, i)) junction_num += 1 - if len(junction_data) > 0: - road_end_dist = self._accum_dist[self._route_length - 1] - if junction_data[-1].route_exit_index: - route_start_dist = self._accum_dist[junction_data[-1].route_exit_index] - else: - route_start_dist = self._accum_dist[self._route_length - 1] - junction_data[-1].exit_road_length = road_end_dist - route_start_dist - return junction_data def _filter_fake_junctions(self, data): """ - Filters fake junctions. As a general note, a fake junction is that where no road lane divide in two. - However, this might fail for some CARLA maps, so check junctions which have all lanes straight too + Filters fake junctions. A fake junction is that which has no intersecting maneuvers + (i.e, no two maneuvers start / end at the same lane). + However, this fails for highway entry / exits with dedicated lanes, so specifically check those """ fake_data = [] filtered_data = [] - threshold = math.radians(15) for junction_data in data: - used_entry_lanes = [] - used_exit_lanes = [] - for junction in junction_data.junctions: - for entry_wp, exit_wp in junction.get_waypoints(carla.LaneType.Driving): - entry_wp = self._get_junction_entry_wp(entry_wp) - if not entry_wp: - continue - if get_lane_key(entry_wp) not in used_entry_lanes: - used_entry_lanes.append(get_lane_key(entry_wp)) + if len (junction_data.junctions) > 1: + filtered_data.append(junction_data) + continue # These are always junctions - exit_wp = self._get_junction_exit_wp(exit_wp) - if not exit_wp: - continue - if get_lane_key(exit_wp) not in used_exit_lanes: - used_exit_lanes.append(get_lane_key(exit_wp)) + junction = junction_data.junctions[0] + + found_intersecting_maneuvers = False + used_entries = [] + used_exits = [] + + # Search for intersecting maneuvers + for entry_wp, exit_wp in junction.get_waypoints(carla.LaneType.Driving): + entry_key = get_lane_key(self._get_junction_entry_wp(entry_wp)) + exit_key = get_lane_key(self._get_junction_exit_wp(exit_wp)) + + # Check if a maneuver starts / ends at another one. + # Checking if it was used isn't enough as some maneuvers are repeated in CARLA maps. + # Instead, check if the index of both entry and exit are different. + entry_index = -1 if entry_key not in used_entries else used_entries.index(entry_key) + exit_index = -1 if exit_key not in used_exits else used_exits.index(exit_key) + + if exit_index != entry_index: + found_intersecting_maneuvers = True + break - if not used_entry_lanes and not used_exit_lanes: - fake_data.append(junction_data) + used_entries.append(entry_key) + used_exits.append(exit_key) + + if found_intersecting_maneuvers: + filtered_data.append(junction_data) continue - found_turn = False - for entry_wp, exit_wp in junction_data.junctions[0].get_waypoints(carla.LaneType.Driving): - entry_heading = entry_wp.transform.get_forward_vector() - exit_heading = exit_wp.transform.get_forward_vector() - dot = entry_heading.x * exit_heading.x + entry_heading.y * exit_heading.y - if dot < math.cos(threshold): - found_turn = True + # Search for highway dedicated lane entries. + found_highway = False + used_entry_roads = {} + used_exit_roads = {} + for entry_wp, exit_wp in junction.get_waypoints(carla.LaneType.Driving): + entry_road_key = get_road_key(self._get_junction_entry_wp(entry_wp)) + exit_road_key = get_road_key(self._get_junction_exit_wp(exit_wp)) + + # Entries / exits with dedicated lanes have no intersecting maneuvers + # (as the entry / exit is a lane that finishes, not a maneuvers part of a junction), + # so they are missfiltered as fake junctions. + # Detect them by an entry road having 3 or more lanes. TODO: Improve this + if entry_road_key in used_entry_roads: + used_entry_roads[entry_road_key] += 1 + else: + used_entry_roads[entry_road_key] = 0 + + if exit_road_key in used_exit_roads: + used_exit_roads[exit_road_key] += 1 + else: + used_exit_roads[exit_road_key] = 0 + + if used_entry_roads[entry_road_key] >= 3 or used_exit_roads[exit_road_key] >= 3: + found_highway = True break - if not found_turn: - fake_data.append(junction_data) - else: + if found_highway: filtered_data.append(junction_data) + continue + + fake_data.append(junction_data) + + # TODO: Recheck for old CARLA maps return fake_data, filtered_data @@ -732,7 +672,7 @@ def _add_junctions_topology(self, route_data): used_entry_lanes.append(get_lane_key(entry_wp)) entry_lane_wps.append(entry_wp) if self.debug: - draw_point(self._world, entry_wp.transform.location, 'small', 'entry', True) + draw_point(self._world, entry_wp.transform.location, DEBUG_SMALL, DEBUG_ENTRY, True) exit_wp = self._get_junction_exit_wp(exit_wp) if not exit_wp: @@ -741,7 +681,7 @@ def _add_junctions_topology(self, route_data): used_exit_lanes.append(get_lane_key(exit_wp)) exit_lane_wps.append(exit_wp) if self.debug: - draw_point(self._world, exit_wp.transform.location, 'small', 'exit', True) + draw_point(self._world, exit_wp.transform.location, DEBUG_SMALL, DEBUG_EXIT, True) # Check for connecting lanes. This is pretty much for the roundabouts, but some weird geometries # make it possible for single junctions to have the same road entering and exiting. Two cases, @@ -752,13 +692,13 @@ def _add_junctions_topology(self, route_data): if get_lane_key(wp) in exit_lane_keys: entry_lane_wps.remove(wp) if self.debug: - draw_point(self._world, wp.transform.location, 'small', 'connect', True) + draw_point(self._world, wp.transform.location, DEBUG_SMALL, DEBUG_CONNECT, True) for wp in list(exit_lane_wps): if get_lane_key(wp) in entry_lane_keys: exit_lane_wps.remove(wp) if self.debug: - draw_point(self._world, wp.transform.location, 'small', 'connect', True) + draw_point(self._world, wp.transform.location, DEBUG_SMALL, DEBUG_CONNECT, True) # Lanes with a fake junction in the middle (maps junction exit to fake junction entry and viceversa) for entry_key, exit_key in self._fake_lane_pair_keys: @@ -776,11 +716,20 @@ def _add_junctions_topology(self, route_data): entry_lane_wps.remove(entry_wp) exit_lane_wps.remove(exit_wp) if self.debug: - draw_point(self._world, entry_wp.transform.location, 'small', 'connect', True) - draw_point(self._world, exit_wp.transform.location, 'small', 'connect', True) + draw_point(self._world, entry_wp.transform.location, DEBUG_SMALL, DEBUG_CONNECT, True) + draw_point(self._world, exit_wp.transform.location, DEBUG_SMALL, DEBUG_CONNECT, True) junction_data.entry_wps = entry_lane_wps junction_data.exit_wps = exit_lane_wps + junction_data.entry_lane_keys = entry_lane_keys + junction_data.exit_lane_keys = exit_lane_keys + for exit_wp in exit_lane_wps: + junction_data.exit_dict[get_lane_key(exit_wp)] = { + 'actors': [], + 'max_actors': 0, + 'ref_wp': None, + 'max_distance': 0, + } # Filter the entries and exits that correspond to the route route_entry_wp = self._route[junction_data.route_entry_index] @@ -843,34 +792,30 @@ def _add_junctions_topology(self, route_data): entry_print = '> J Entry Lanes: ' for entry_wp in entry_lane_wps: key = get_lane_key(entry_wp) - entry_print += key + ' ' * (6 - len(key)) + entry_print += key + ' ' * (8 - len(key)) print(entry_print) exit_print = '> J Exit Lanes: ' for exit_wp in exit_lane_wps: key = get_lane_key(exit_wp) - exit_print += key + ' ' * (6 - len(key)) + exit_print += key + ' ' * (8 - len(key)) print(exit_print) route_entry = '> R-J Entry Lanes: ' for entry_key in junction_data.route_entry_keys: - route_entry += entry_key + ' ' * (6 - len(entry_key)) + route_entry += entry_key + ' ' * (8 - len(entry_key)) print(route_entry) route_exit = '> R-J Route Exit Lanes: ' for exit_key in junction_data.route_exit_keys: - route_exit += exit_key + ' ' * (6 - len(exit_key)) + route_exit += exit_key + ' ' * (8 - len(exit_key)) print(route_exit) route_oppo_entry = '> R-J Oppo Entry Lanes: ' for oppo_entry_key in junction_data.opposite_entry_keys: - route_oppo_entry += oppo_entry_key + ' ' * (6 - len(oppo_entry_key)) + route_oppo_entry += oppo_entry_key + ' ' * (8 - len(oppo_entry_key)) print(route_oppo_entry) route_oppo_exit = '> R-J Oppo Exit Lanes: ' for oppo_exit_key in junction_data.opposite_exit_keys: - route_oppo_exit += oppo_exit_key + ' ' * (6 - len(oppo_exit_key)) + route_oppo_exit += oppo_exit_key + ' ' * (8 - len(oppo_exit_key)) print(route_oppo_exit) - ################################ - ## Waypoint related functions ## - ################################ - def _is_junction(self, waypoint): if not waypoint.is_junction or waypoint.junction_id in self._fake_junction_ids: return False @@ -881,231 +826,113 @@ def _is_junction(self, waypoint): ################################ def _add_actor_dict_element(self, actor_dict, actor, exit_lane_key='', at_oppo_entry_lane=False): - """Adds a new actor to the actor dictionary""" + """ + Adds a new actor to the actor dictionary. + 'exit_lane_key' is used to know at which exit lane (if any) is the vehicle + 'at_oppo_entry_lane' whether or not the actor is part of the entry at the opposite lane the route exits through. + This will be the ones that aren't removed + """ actor_dict[actor] = { - 'state': 'junction_entry' if not exit_lane_key else 'junction_exit', + 'state': JUNCTION_ENTRY if not exit_lane_key else JUNCTION_EXIT, 'exit_lane_key': exit_lane_key, 'at_oppo_entry_lane': at_oppo_entry_lane } def _switch_to_junction_mode(self, junction): - """Prepares the junction mode, changing the state of the actors""" - self._ego_state = 'junction' - for actor in list(self._road_actors): - self._add_actor_dict_element(junction.actor_dict, actor) - self._road_actors.remove(actor) - if not self._is_scenario_2_active: - self._tm.vehicle_percentage_speed_difference(actor, 0) - - self._road_back_actors.clear() - self._road_extra_front_actors = 0 - self._opposite_sources.clear() - - def _initialise_junction_scenario(self, direction, remove_entries, remove_exits, remove_middle): - """ - Removes all vehicles in a particular 'direction' as well as all actors inside the junction. - Additionally, activates some flags to ensure the junction is empty at all times - """ - if self._active_junctions: - scenario_junction = self._active_junctions[0] - scenario_junction.scenario_info = { - 'direction': direction, - 'remove_entries': remove_entries, - 'remove_middle': remove_middle, - 'remove_exits': remove_exits, - } - entry_direction_keys = scenario_junction.entry_directions[direction] - actor_dict = scenario_junction.actor_dict - - if remove_entries: - for entry_source in scenario_junction.entry_sources: - if get_lane_key(entry_source.entry_lane_wp) in entry_direction_keys: - # Source is affected - actors = entry_source.actors - for actor in list(actors): - if actor_dict[actor]['state'] == 'junction_entry': - # Actor is at the entry lane - self._destroy_actor(actor) - - if remove_exits: - for exit_dir in scenario_junction.exit_directions[direction]: - for actor in list(scenario_junction.exit_dict[exit_dir]['actors']): - self._destroy_actor(actor) - - if remove_middle: - actor_dict = scenario_junction.actor_dict - for actor in list(actor_dict): - if actor_dict[actor]['state'] == 'junction_middle': - self._destroy_actor(actor) - - def _handle_junction_scenario_end(self, junction): - """Ends the junction scenario interaction. This is pretty much useless as the junction - scenario ends at the same time as the active junction, but in the future it might not""" - junction.scenario_info = { - 'direction': None, - 'remove_entries': False, - 'remove_middle': False, - 'remove_exits': False, - } - - def _monitor_scenario_4_end(self, ego_location): - """Monitors the ego distance to the junction to know if the scenario 4 has ended""" - if self._ego_exitted_junction: - ref_location = self._start_ego_wp.transform.location - if ego_location.distance(ref_location) > self._crossing_dist: - for actor in self._scenario_4_actors: - self._tm.vehicle_percentage_speed_difference(actor, 0) - self._is_scenario_4_active = False - self._scenario_4_actors.clear() - self._ego_exitted_junction = False - self._crossing_dist = None - - def _handle_scenario_4_interaction(self, junction, ego_wp): """ - Handles the interation between the scenario 4 of the Leaderboard and the background activity. - This removes all vehicles near the bycicle path, and stops the others so that they don't interfere + Prepares the junction mode, removing all road behaviours. + Actors that are stopped via a scenario will still wait. """ - if not self._is_scenario_4_active: - return - - self._ego_exitted_junction = True - self._start_ego_wp = ego_wp - min_crossing_space = 2 - - # Actor exitting the junction - exit_dict = junction.exit_dict - for exit_key in exit_dict: - if exit_key not in junction.route_exit_keys: - continue - actors = exit_dict[exit_key]['actors'] - exit_lane_wp = exit_dict[exit_key]['ref_wp'] - exit_lane_location = exit_lane_wp.transform.location - for actor in list(actors): - actor_location = CarlaDataProvider.get_location(actor) - if not actor_location: - self._destroy_actor(actor) - continue - - dist_to_scenario = exit_lane_location.distance(actor_location) - self._crossing_dist - actor_length = actor.bounding_box.extent.x - if abs(dist_to_scenario) < actor_length + min_crossing_space: - self._destroy_actor(actor) - continue - - if dist_to_scenario > 0: - continue # Don't stop the actors that have already passed the scenario - - if get_lane_key(ego_wp) == get_lane_key(exit_lane_wp): - self._destroy_actor(actor) - continue # Actor at the ego lane and between the ego and scenario - - self._scenario_4_actors.append(actor) - - # Actor entering the junction - for entry_source in junction.entry_sources: - entry_lane_wp = entry_source.entry_lane_wp - if get_lane_key(entry_lane_wp) in junction.opposite_entry_keys: - # Source is affected - actors = entry_source.actors - entry_lane_location = entry_lane_wp.transform.location - for actor in list(actors): - actor_location = CarlaDataProvider.get_location(actor) - if not actor_location: - self._destroy_actor(actor) - continue - - crossing_space = abs(entry_lane_location.distance(actor_location) - self._crossing_dist) - actor_length = actor.bounding_box.extent.x - if crossing_space < actor_length + min_crossing_space: + self._ego_state = EGO_JUNCTION + for lane in self._road_dict: + for actor in self._road_dict[lane].actors: + # TODO: Map the actors to the junction entry to have full control of them. + # This should remove the 'at_oppo_entry_lane'. + self._add_actor_dict_element(junction.actor_dict, actor) + if actor not in self._scenario_stopped_actors: + self._actors_speed_perc[actor] = 100 + + for lane_key in self._road_dict: + source = self._road_dict[lane_key] + source_key = get_lane_key(source.wp) + if source_key in junction.route_entry_keys: + junction.entry_sources.append(Source( + source.wp, source.actors, entry_lane_wp=source.wp, active=source.active) + ) + if source_key in junction.inactive_entry_keys: + for actor in source.actors: self._destroy_actor(actor) - continue # Actors blocking the path of the crossing obstacle - - self._scenario_4_actors.append(actor) - - # Actors entering the next junction - if len(self._active_junctions) > 1: - next_junction = self._active_junctions[1] - actors_dict = next_junction.actor_dict - for actor in list(actors_dict): - if actors_dict[actor]['state'] != 'junction_entry': - continue - - actor_location = CarlaDataProvider.get_location(actor) - if not actor_location: - self._destroy_actor(actor) - continue - - dist_to_scenario = exit_lane_location.distance(actor_location) - self._crossing_dist - actor_length = actor.bounding_box.extent.x - if abs(dist_to_scenario) < actor_length + min_crossing_space: - self._destroy_actor(actor) - continue + source.active = False + junction.inactive_entry_keys.remove(source_key) - if dist_to_scenario > 0: - continue # Don't stop the actors that have already passed the scenario + # TODO: Else should map the source to the entry and add it - actor_wp = self._map.get_waypoint(actor_location) - if get_lane_key(ego_wp) == get_lane_key(actor_wp): - self._destroy_actor(actor) - continue # Actor at the ego lane and between the ego and scenario - - self._scenario_4_actors.append(actor) - - # Immediately freeze the actors - for actor in self._scenario_4_actors: - try: - actor.set_target_velocity(carla.Vector3D(0, 0, 0)) - self._tm.vehicle_percentage_speed_difference(actor, 100) - except RuntimeError: - pass # Just in case the actor is not alive + self._road_dict.clear() + self._opposite_sources.clear() - def _end_junction_behavior(self, ego_wp, junction): + def _end_junction_behavior(self, junction): """ - Destroys unneeded actors (those behind the ego), moves the rest to other data structures - and cleans up the variables. If no other junctions are active, starts road mode + Destroys unneeded actors (those that aren't part of the route's road), + moving the rest to other data structures and cleaning up the variables. + If no other junctions are active, starts road mode """ actor_dict = junction.actor_dict route_exit_keys = junction.route_exit_keys self._active_junctions.pop(0) + # Prepare the road dictionary + if not self._active_junctions: + for wp in junction.exit_wps: + if get_lane_key(wp) in route_exit_keys: + self._road_dict[get_lane_key(wp)] = Source(wp, [], active=self._active_road_sources) + + else: + for wp in junction.exit_wps: + if get_lane_key(wp) in route_exit_keys: + # TODO: entry_lane_wp isn't really this one (for cases with road changes) + self._active_junctions[0].entry_sources.append( + Source(wp, [], entry_lane_wp=wp, active=self._active_road_sources) + ) + + # Handle the actors for actor in list(actor_dict): location = CarlaDataProvider.get_location(actor) if not location or self._is_location_behind_ego(location): self._destroy_actor(actor) continue - self._tm.vehicle_percentage_speed_difference(actor, 0) + # Don't destroy those that are on the route's road opposite lane. + # Instead, let them move freely until they are automatically destroyed. + self._actors_speed_perc[actor] = 100 if actor_dict[actor]['at_oppo_entry_lane']: self._opposite_actors.append(actor) self._tm.ignore_lights_percentage(actor, 100) self._tm.ignore_signs_percentage(actor, 100) continue - if not self._active_junctions and actor_dict[actor]['exit_lane_key'] in route_exit_keys: - self._road_actors.append(actor) + # Save those that are on the route's road + exit_key = actor_dict[actor]['exit_lane_key'] + if exit_key in route_exit_keys: + if not self._active_junctions: + self._road_dict[exit_key].actors.append(actor) + else: + entry_sources = self._active_junctions[0].entry_sources + for entry_source in entry_sources: # Add it to the back source + if exit_key == get_lane_key(entry_source.wp): + entry_sources.actors.append(actor) + break continue + # Destroy the rest self._destroy_actor(actor) - self._handle_scenario_4_interaction(junction, ego_wp) - self._handle_junction_scenario_end(junction) - self._switch_junction_road_sources(junction) + # If the junction was part of a scenario, forget about it + self._scenario_junction_entry = False if not self._active_junctions: - self._ego_state = 'road' + self._ego_state = EGO_ROAD self._initialise_opposite_sources() self._initialise_road_checker() - self._road_ego_key = self._get_ego_route_lane_key(ego_wp) - for source in junction.exit_sources: - self._road_back_actors[source.mapped_key] = [] - - def _switch_junction_road_sources(self, junction): - """ - Removes the sources part of the previous road and gets the ones of the exitted junction. - """ - self._road_sources.clear() - new_sources = junction.exit_sources - self._road_sources.extend(new_sources) def _search_for_next_junction(self): """Check if closeby to a junction. The closest one will always be the first""" @@ -1114,7 +941,7 @@ def _search_for_next_junction(self): ego_accum_dist = self._accum_dist[self._route_index] junction_accum_dist = self._accum_dist[self._junctions[0].route_entry_index] - if junction_accum_dist - ego_accum_dist < self._junction_detection_dist: # Junctions closeby + if junction_accum_dist - ego_accum_dist < self._detection_dist: # Junctions closeby return self._junctions.pop(0) return None @@ -1133,32 +960,37 @@ def _initialise_connecting_lanes(self, junction): for actor in list(exit_actors): self._remove_actor_info(actor) self._add_actor_dict_element(junction.actor_dict, actor) - self._tm.vehicle_percentage_speed_difference(actor, 0) + self._actors_speed_perc[actor] = 100 - def _monitor_nearby_junctions(self): + def _monitor_incoming_junctions(self): """ - Monitors when the ego approaches a junction, preparing the junction mode when it happens. - This can be triggered even if there is another junction behavior happening + Monitors when the ego approaches a junction, triggering that junction when it happens. + This can be triggered even if there is another junction happening are they work independently """ junction = self._search_for_next_junction() if not junction: return - if self._ego_state == 'road': + if self._ego_state == EGO_ROAD: self._switch_to_junction_mode(junction) self._initialise_junction_sources(junction) self._initialise_junction_exits(junction) + self._initialise_connecting_lanes(junction) self._active_junctions.append(junction) - def _monitor_ego_junction_exit(self, ego_wp): + # Forget the fact that a lane was removed, so that it isn't readded in the middle of the junction + self._scenario_removed_lane = False + self._scenario_remove_lane_offset = 0 + + def _monitor_ego_junction_exit(self): """ Monitors when the ego exits the junctions, preparing the road mode when that happens """ current_junction = self._active_junctions[0] exit_index = current_junction.route_exit_index if exit_index and self._route_index >= exit_index: - self._end_junction_behavior(ego_wp, current_junction) + self._end_junction_behavior(current_junction) def _add_incoming_actors(self, junction, source): """Checks nearby actors that will pass through the source, adding them to it""" @@ -1167,7 +999,7 @@ def _add_incoming_actors(self, junction, source): source.previous_lane_keys = [get_lane_key(prev_wp) for prev_wp in source.wp.previous(self._reuse_dist)] source.previous_lane_keys.append(get_lane_key(source.wp)) - for actor in self._get_actors(): + for actor in self._all_actors: if actor in source.actors: continue # Don't use actors already part of the source @@ -1181,7 +1013,7 @@ def _add_incoming_actors(self, junction, source): if get_lane_key(actor_wp) not in source.previous_lane_keys: continue # Don't use actors that won't pass through the source - self._tm.vehicle_percentage_speed_difference(actor, 0) + self._actors_speed_perc[actor] = 100 self._remove_actor_info(actor) source.actors.append(actor) @@ -1190,22 +1022,75 @@ def _add_incoming_actors(self, junction, source): return actor - def _update_road_sources(self, ego_location): + def _move_road_sources(self, prev_ego_index): + """ + Moves the road sources so that they are always following the ego from behind + """ + if prev_ego_index != self._route_index: + min_distance = self._road_back_vehicles * self._road_spawn_dist + for lane_key in self._road_dict: + source = self._road_dict[lane_key] + + # If no actors are found, let the last_location be ego's location + # to keep moving the source waypoint forward + if len(source.actors) == 0: + last_location = self._ego_wp.transform.location + else: + last_location = CarlaDataProvider.get_location(source.actors[-1]) + + if last_location is None: + continue + + # Stop the sources in front of the ego (created by new lanes) + if not self._is_location_behind_ego(source.wp.transform.location): + continue + + # Stop the source from being too close to the ego or last lane vehicle + source_location = source.wp.transform.location + ego_location = self._ego_wp.transform.location + + actor_dist = max(0, last_location.distance(source_location) - self._road_spawn_dist) + ego_dist = max(0, ego_location.distance(source_location) - min_distance) + move_dist = min(actor_dist, ego_dist) + + # Move the source forward if needed + if move_dist > 0: + new_source_wps = source.wp.next(move_dist) + if not new_source_wps: + continue + source.wp = new_source_wps[0] + + def _update_road_sources(self): """ Manages the sources that spawn actors behind the ego. - Sources are destroyed after their actors are spawned + These are always behind the ego and will continuously spawn actors. + These sources also track the amount of vehicles in front of the ego, + removing actors if the amount is too high. """ - for source in list(self._road_sources): + for lane_key in self._road_dict: + source = self._road_dict[lane_key] if self.debug: - draw_point(self._world, source.wp.transform.location, 'small', self._ego_state, False) - draw_string(self._world, source.wp.transform.location, str(len(source.actors)), self._ego_state, False) + draw_point(self._world, source.wp.transform.location, DEBUG_SMALL, DEBUG_ROAD, False) + draw_string(self._world, source.wp.transform.location, str(len(source.actors)), DEBUG_ROAD, False) - if len(source.actors) >= self._road_back_vehicles: - self._road_sources.remove(source) + # Ensure not too many actors are in front of the ego + front_veh = 0 + for actor in source.actors: + location = CarlaDataProvider.get_location(actor) + if location and not self._is_location_behind_ego(location): + front_veh += 1 + if front_veh > self._road_front_vehicles: + self._destroy_actor(source.actors[0]) # This is always the front most vehicle + + if not source.active: + continue + if not self._is_location_behind_ego(source.wp.transform.location): + continue # Stop the sources in front of the ego (created by new lanes) + if len(source.actors) >= self._road_back_vehicles + self._road_front_vehicles: continue if len(source.actors) == 0: - location = ego_location + location = self._ego_wp.transform.location else: location = CarlaDataProvider.get_location(source.actors[-1]) if not location: @@ -1215,56 +1100,45 @@ def _update_road_sources(self, ego_location): # Spawn a new actor if the last one is far enough if distance > self._road_spawn_dist: - actor = self._spawn_source_actor(source, ego_dist=self._road_vehicle_dist) + actor = self._spawn_source_actor(source, self._road_spawn_dist) if actor is None: continue - self._tm.distance_to_leading_vehicle(actor, self._road_vehicle_dist) + # Set their initial speed, so that they don't lag behind the ego. + # Set the speed to the ego's one, but never surpassing by the lane's last vehicle's one + forward_vec = source.wp.transform.get_forward_vector() + speed = self._ego_actor.get_velocity().length() + if len(source.actors): + speed = min(speed, source.actors[-1].get_velocity().length()) + actor.set_target_velocity(speed * forward_vec) + source.actors.append(actor) - if self._ego_state == 'road': - self._road_actors.append(actor) - if source.mapped_key in self._road_back_actors: - self._road_back_actors[source.mapped_key].append(actor) - elif self._ego_state == 'junction': - self._add_actor_dict_element(self._active_junctions[0].actor_dict, actor) ################################ ## Behavior related functions ## ################################ - def _initialise_road_behavior(self, road_wps, ego_wp): - """Initialises the road behavior, consisting on several vehicle in front of the ego, - and several on the back. The ones on the back are spawned only outside junctions, - and if not enough are spawned, sources are created that will do so later on""" - spawn_wps = [] + def _initialise_road_behavior(self, road_wps): + """ + Initialises the road behavior, consisting on several vehicle in front of the ego, + and several on the back and are only spawned outside junctions. + If there aren't enough actors behind, road sources will be created that will do so later on + """ # Vehicles in front for wp in road_wps: + spawn_wps = [] + + # Front spawn points next_wp = wp for _ in range(self._road_front_vehicles): next_wps = next_wp.next(self._road_spawn_dist) if len(next_wps) != 1 or self._is_junction(next_wps[0]): break # Stop when there's no next or found a junction next_wp = next_wps[0] - spawn_wps.append(next_wp) - - for actor in self._spawn_actors(spawn_wps): - self._tm.distance_to_leading_vehicle(actor, self._road_vehicle_dist) - self._road_actors.append(actor) - - # Vehicles on the side - for wp in road_wps: - self._road_back_actors[get_lane_key(wp)] = [] - if wp.lane_id == ego_wp.lane_id: - continue + spawn_wps.insert(0, next_wp) - actor = self._spawn_actors([wp])[0] - self._tm.distance_to_leading_vehicle(actor, self._road_vehicle_dist) - self._road_actors.append(actor) - self._road_back_actors[get_lane_key(wp)].append(actor) - - # Vehicles behind - for wp in road_wps: - spawn_wps = [] + # Back spawn points + source_dist = 0 prev_wp = wp for _ in range(self._road_back_vehicles): prev_wps = prev_wp.previous(self._road_spawn_dist) @@ -1272,21 +1146,21 @@ def _initialise_road_behavior(self, road_wps, ego_wp): break # Stop when there's no next or found a junction prev_wp = prev_wps[0] spawn_wps.append(prev_wp) + source_dist += self._road_spawn_dist + # Spawn actors actors = self._spawn_actors(spawn_wps) - for actor in actors: - self._tm.distance_to_leading_vehicle(actor, self._road_vehicle_dist) - self._road_actors.append(actor) - self._road_back_actors[get_lane_key(wp)].append(actor) - # If not spawned enough, create actor soruces behind the ego - if len(actors) < self._road_back_vehicles: - self._road_sources.append(Source(prev_wp, actors)) + self._road_dict[get_lane_key(wp)] = Source( + prev_wp, actors, active=self._active_road_sources + ) def _initialise_opposite_sources(self): """ - Gets the waypoints where the actor sources that spawn actors in the opposite direction - will be located. These are at a fixed distance from the ego, but never entering junctions + All opposite lanes have actor sources that will continually create vehicles, + creating the sensation of permanent traffic. The actor spawning will be done later on + (_update_opposite_sources). These sources are at a (somewhat) fixed distance + from the ego, but they never entering junctions. """ self._opposite_route_index = None if not self._junctions: @@ -1306,7 +1180,7 @@ def _initialise_opposite_sources(self): oppo_wp = self._route[self._opposite_route_index] for wp in get_opposite_dir_lanes(oppo_wp): - self._opposite_sources.append(Source(wp, [])) + self._opposite_sources.append(Source(wp, [], active=self._active_opposite_sources)) def _initialise_road_checker(self): """ @@ -1331,20 +1205,13 @@ def _initialise_junction_sources(self, junction): """ Initializes the actor sources to ensure the junction is always populated. They are placed at certain distance from the junction, but are stopped if another junction is found, - to ensure the spawned actors always move towards the activated one + to ensure the spawned actors always move towards the activated one. """ - remove_entries = junction.scenario_info['remove_entries'] - direction = junction.scenario_info['direction'] - entry_lanes = [] if not direction else junction.entry_directions[direction] - for wp in junction.entry_wps: entry_lane_key = get_lane_key(wp) if entry_lane_key in junction.route_entry_keys: continue # Ignore the road from which the route enters - if remove_entries and entry_lane_key in entry_lanes: - continue # Ignore entries that are part of active junction scenarios - moved_dist = 0 prev_wp = wp while moved_dist < self._junction_sources_dist: @@ -1354,7 +1221,23 @@ def _initialise_junction_sources(self, junction): prev_wp = prev_wps[0] moved_dist += 5 - junction.entry_sources.append(Source(prev_wp, [], entry_lane_wp=wp)) + # Don't add junction sources too close to the junction + if moved_dist < self._junction_minimum_source_dist: + continue + + source = Source(prev_wp, [], entry_lane_wp=wp) + entry_lane_key = get_lane_key(wp) + if entry_lane_key in junction.inactive_entry_keys: + source.active = False + junction.inactive_entry_keys.remove(entry_lane_key) + + junction.entry_sources.append(source) + + # Real junctions aren't always full of traffic in all lanes, so deactivate some of them. + # Doing this after the source have been created in case another behavior activates them + for source in junction.entry_sources: + if 100 * self._rng.random() > self._junction_source_perc: + source.active = False def _initialise_junction_exits(self, junction): """ @@ -1364,16 +1247,20 @@ def _initialise_junction_exits(self, junction): exit_wps = junction.exit_wps route_exit_keys = junction.route_exit_keys - remove_exits = junction.scenario_info['remove_exits'] - direction = junction.scenario_info['direction'] - exit_lanes = [] if not direction else junction.exit_directions[direction] - for wp in exit_wps: max_actors = 0 - max_distance = 0 + max_distance = junction.exit_dict[get_lane_key(wp)]['max_distance'] exiting_wps = [] next_wp = wp + + # Move the initial distance (added by the `_extent_road_exit_space`) + if max_distance > 0: + next_wps = next_wp.next(max_distance) + if not next_wps: + continue + next_wp = next_wps[0] + for i in range(max(self._road_front_vehicles, 1)): # Get the moving distance (first jump is higher to allow space for another vehicle) @@ -1399,36 +1286,28 @@ def _initialise_junction_exits(self, junction): } exit_lane_key = get_lane_key(wp) - if remove_exits and exit_lane_key in exit_lanes: - continue # The direction is prohibited as a junction scenario is active + if exit_lane_key in junction.inactive_exit_keys: + continue # The exit is inactive, don't spawn anything if exit_lane_key in route_exit_keys: - junction.exit_sources.append(Source(wp, [])) - actors = self._spawn_actors(exiting_wps) for actor in actors: - self._tm.distance_to_leading_vehicle(actor, self._junction_vehicle_dist) self._add_actor_dict_element(junction.actor_dict, actor, exit_lane_key=exit_lane_key) junction.exit_dict[exit_lane_key]['actors'] = actors def _update_junction_sources(self): """Checks the actor sources to see if new actors have to be created""" for junction in self._active_junctions: - remove_entries = junction.scenario_info['remove_entries'] - direction = junction.scenario_info['direction'] - entry_lanes = [] if not direction else junction.entry_directions[direction] - actor_dict = junction.actor_dict for source in junction.entry_sources: if self.debug: - draw_point(self._world, source.wp.transform.location, 'small', 'junction', False) - draw_string(self._world, source.wp.transform.location, str(len(source.actors)), 'junction', False) + draw_point(self._world, source.wp.transform.location, DEBUG_SMALL, DEBUG_JUNCTION, False) + draw_string(self._world, source.wp.transform.location, str(len(source.actors)), DEBUG_JUNCTION, False) entry_lane_key = get_lane_key(source.entry_lane_wp) at_oppo_entry_lane = entry_lane_key in junction.opposite_entry_keys - # The direction is prohibited as a junction scenario is active - if remove_entries and entry_lane_key in entry_lanes: + if not source.active: continue self._add_incoming_actors(junction, source) @@ -1439,56 +1318,71 @@ def _update_junction_sources(self): # Calculate distance to the last created actor if len(source.actors) == 0: - distance = self._junction_spawn_dist + 1 + actor_location = CarlaDataProvider.get_location(self._ego_actor) else: actor_location = CarlaDataProvider.get_location(source.actors[-1]) - if not actor_location: - continue - distance = actor_location.distance(source.wp.transform.location) + + if not actor_location: + continue + distance = actor_location.distance(source.wp.transform.location) # Spawn a new actor if the last one is far enough if distance > self._junction_spawn_dist: - actor = self._spawn_source_actor(source) + actor = self._spawn_source_actor(source, self._junction_spawn_dist) if not actor: continue - - self._tm.distance_to_leading_vehicle(actor, self._junction_vehicle_dist) + if junction.stop_non_route_entries and get_lane_key(source.entry_lane_wp) not in junction.route_entry_keys: + self._actors_speed_perc[actor] = 0 self._add_actor_dict_element(actor_dict, actor, at_oppo_entry_lane=at_oppo_entry_lane) source.actors.append(actor) - def _found_a_road_change(self, old_index, new_index, ignore_false_junctions=True): - """Checks if the new route waypoint is part of a new road (excluding fake junctions)""" - if new_index == old_index: - return False - - new_wp = self._route[new_index] - old_wp = self._route[old_index] - if get_road_key(new_wp) == get_road_key(old_wp): - return False - - if ignore_false_junctions: - new_wp_junction = new_wp.get_junction() - if new_wp_junction and new_wp_junction.id in self._fake_junction_ids: - return False - - return True - - def _move_road_checker(self, prev_index, current_index): + def _monitor_topology_changes(self, prev_index): """ Continually check the road in front to see if it has changed its topology. - If so and the number of lanes have reduced, remove the actor of the lane that merges into others + If the number of lanes reduces, merge the ending lane with a side one + If the number of lanes increases, add a new road source. """ + def get_road_wp(wp): + """Goes backwards in the lane to match the wp with the road key dictionary""" + road_wp = wp + if get_lane_key(road_wp) in self._road_dict: + return road_wp + + road_wp = wp + distance = self._max_radius + + while distance > 0: + prev_wps = road_wp.previous(1) + if not prev_wps: + return None + road_wp = prev_wps[0] + if get_lane_key(road_wp) in self._road_dict: + return road_wp + distance -= 1 + + return None + + def get_source_wp(wp): + """Moves the wp forward until the lane is wide enough to fit a vehicle""" + source_wp = wp + while source_wp.lane_width < self._lane_width_threshold + 0.2: + source_wps = source_wp.next(1) + if not source_wps: + return None + source_wp = source_wps[0] + return source_wp + if self.debug: checker_wp = self._route[self._road_checker_index] - draw_point(self._world, checker_wp.transform.location, 'small', 'road', False) + draw_point(self._world, checker_wp.transform.location, DEBUG_SMALL, DEBUG_ROAD, False) - if prev_index == current_index: + if prev_index == self._route_index: return # Get the new route tracking wp checker_index = None last_index = self._junctions[0].route_entry_index if self._junctions else self._route_length - 1 - current_accum_dist = self._accum_dist[current_index] + current_accum_dist = self._accum_dist[self._route_index] for i in range(self._road_checker_index, last_index): accum_dist = self._accum_dist[i] if accum_dist - current_accum_dist >= self._max_radius: @@ -1497,58 +1391,141 @@ def _move_road_checker(self, prev_index, current_index): if not checker_index: checker_index = last_index - if self._found_a_road_change(self._road_checker_index, checker_index): - new_wps = get_same_dir_lanes(self._route[checker_index]) - old_wps = get_same_dir_lanes(self._route[self._road_checker_index]) + if checker_index == self._road_checker_index: + return - if len(new_wps) >= len(old_wps): - pass - else: - new_accum_dist = self._accum_dist[checker_index] - prev_accum_dist = self._accum_dist[self._road_checker_index] - route_move_dist = new_accum_dist - prev_accum_dist - unmapped_lane_keys = [] - - for old_wp in list(old_wps): - location = old_wp.transform.location - mapped_wp = None - for new_wp in new_wps: - if location.distance(new_wp.transform.location) < 1.1 * route_move_dist: - mapped_wp = new_wp - break + new_wps = get_same_dir_lanes(self._route[checker_index]) + old_wps = get_same_dir_lanes(self._route[self._road_checker_index]) - if not mapped_wp: - unmapped_lane_keys.append(get_lane_key(old_wp)) + new_accum_dist = self._accum_dist[checker_index] + prev_accum_dist = self._accum_dist[self._road_checker_index] + route_move_dist = max(new_accum_dist - prev_accum_dist, 0.1) - for actor in list(self._road_actors): - location = CarlaDataProvider.get_location(actor) - if not location: - continue - wp = self._map.get_waypoint(location) - if get_lane_key(wp) in unmapped_lane_keys: - self._destroy_actor(actor) + if len(new_wps) > len(old_wps): + # Don't add anything in front if the junction entry has to be empty + if not self._scenario_junction_entry: - self._road_checker_index = checker_index + for new_wp in list(new_wps): - def _move_opposite_sources(self, prev_index, current_index): - """ - Moves the sources of the opposite direction back. Additionally, tracks a point a certain distance - in front of the ego to see if the road topology has to be recalculated - """ - if self.debug: - for source in self._opposite_sources: - draw_point(self._world, source.wp.transform.location, 'small', 'opposite', False) - draw_string(self._world, source.wp.transform.location, str(len(source.actors)), 'opposite', False) - route_wp = self._route[self._opposite_route_index] - draw_point(self._world, route_wp.transform.location, 'small', 'opposite', False) + prev_wps = new_wp.previous(2 * route_move_dist) # x2, just in case + if prev_wps: + continue - if prev_index == current_index: - return + # Found the new lane, add the actors and source. + # Don't spawn actors while the source is in front of the ego + source_wp = get_source_wp(new_wp) + if not source_wp: + continue - # Get the new route tracking wp + next_wp = source_wp + spawn_wps = [] + spawn_dist = self._road_front_vehicles + CarlaDataProvider.get_velocity(self._ego_actor) + for _ in range(self._road_front_vehicles): + next_wps = next_wp.next(spawn_dist) + if len(next_wps) != 1 or self._is_junction(next_wps[0]): + break # Stop when there's no next or found a junction + next_wp = next_wps[0] + spawn_wps.insert(0, next_wp) + + actors = self._spawn_actors(spawn_wps) + + if get_lane_key(source_wp) not in self._road_dict: + # Lanes created away from the center won't affect the ids of other lanes, so just add the new id + self._road_dict[get_lane_key(source_wp)] = Source(source_wp, actors, active=self._active_road_sources) + else: + # If the lane is inwards, all lanes have their id shifted by 1 outwards + # TODO: Doesn't work for more than one lane. + added_id = 1 if source_wp.lane_id > 0 else -1 + new_lane_key = get_lane_key_from_ids(source_wp.road_id, source_wp.lane_id + added_id) + self._road_dict[new_lane_key] = self._road_dict[get_lane_key(source_wp)] + self._road_dict[get_lane_key(source_wp)] = Source(source_wp, actors, active=self._active_road_sources) + + elif len(new_wps) < len(old_wps): + for old_wp in list(old_wps): + next_wps = old_wp.next(2 * route_move_dist) # x2, just in case + if next_wps: + continue + + # Found the lane that ends, merge it with the one on its side + road_wp = get_road_wp(old_wp) + if not road_wp: + continue + + # Get a side lane + right_wp = old_wp.get_right_lane() + left_wp = old_wp.get_left_lane() + side_road_wp = None + if right_wp and right_wp.lane_type == carla.LaneType.Driving: + side_road_wp = get_road_wp(right_wp) + side_path = [right_wp.transform.location] + elif left_wp and left_wp.lane_type == carla.LaneType.Driving: + side_road_wp = get_road_wp(left_wp) + side_path = [left_wp.transform.location] + + if not side_road_wp: + # No side lane found part of the road dictionary, remove them + for actor in list(self._road_dict[get_lane_key(road_wp)].actors): + self._destroy_actor(actor) + self._road_dict.pop(get_lane_key(road_wp), None) + continue + + # Get the actors + lane_actors = self._road_dict[get_lane_key(road_wp)].actors + side_lane_actors = self._road_dict[get_lane_key(side_road_wp)].actors + + # Get their distance to the ego + actors_with_dist = [] + ego_location = self._ego_wp.transform.location + for actor in lane_actors + side_lane_actors: + actor_location = CarlaDataProvider.get_location(actor) + if not actor_location: + self._destroy_actor(actor) + continue + dist = ego_location.distance(actor_location) + if not self._is_location_behind_ego(actor_location): + dist *= -1 + actors_with_dist.append([actor, dist]) + + # Sort them by distance + actors_sorted_with_dist = sorted(actors_with_dist, key=lambda a: a[1]) + zero_index = len([a for a in actors_sorted_with_dist if a[1] < 0]) + min_index = max(zero_index - self._road_front_vehicles, 0) + max_index = min(zero_index + self._road_front_vehicles - 1, len(actors_sorted_with_dist) - 1) + + # Remove the unneeded ones, and make the ending lane actors perform a lane change + source_actors = [] + for i, (actor, _) in enumerate(actors_sorted_with_dist): + if i >= min_index and i <= max_index: + source_actors.append(actor) + self._tm.set_path(actor, side_path) + else: + self._destroy_actor(actor) + + # And update the road dict + self._road_dict[get_lane_key(side_road_wp)].actors = source_actors + self._road_dict.pop(get_lane_key(road_wp), None) + + self._road_checker_index = checker_index + + def _move_opposite_sources(self, prev_index): + """ + Moves the sources of the opposite direction back. Additionally, tracks a point a certain distance + in front of the ego to see if the road topology has to be recalculated + """ + if self.debug: + for source in self._opposite_sources: + draw_point(self._world, source.wp.transform.location, DEBUG_SMALL, DEBUG_OPPOSITE, False) + draw_string(self._world, source.wp.transform.location, str(len(source.actors)), DEBUG_OPPOSITE, False) + route_wp = self._route[self._opposite_route_index] + draw_point(self._world, route_wp.transform.location, DEBUG_SMALL, DEBUG_OPPOSITE, False) + + if prev_index == self._route_index: + return + + # Get the new route tracking wp oppo_route_index = None last_index = self._junctions[0].route_entry_index if self._junctions else self._route_length - 1 - current_accum_dist = self._accum_dist[current_index] + current_accum_dist = self._accum_dist[self._route_index] for i in range(self._opposite_route_index, last_index): accum_dist = self._accum_dist[i] if accum_dist - current_accum_dist >= self._opposite_sources_dist: @@ -1557,15 +1534,18 @@ def _move_opposite_sources(self, prev_index, current_index): if not oppo_route_index: oppo_route_index = last_index - if self._found_a_road_change(self._opposite_route_index, oppo_route_index): - # Recheck the left lanes as the topology might have changed - new_opposite_sources = [] - new_opposite_wps = get_opposite_dir_lanes(self._route[oppo_route_index]) + # Get the distance moved by the route reference index + new_accum_dist = self._accum_dist[oppo_route_index] + prev_accum_dist = self._accum_dist[self._opposite_route_index] + route_move_dist = new_accum_dist - prev_accum_dist + if route_move_dist <= 0: + return # Sometimes a route wp is behind the pervious one, filter these out + + new_opposite_wps = get_opposite_dir_lanes(self._route[oppo_route_index]) - # Map the old sources to the new wps, and add new ones / remove uneeded ones - new_accum_dist = self._accum_dist[oppo_route_index] - prev_accum_dist = self._accum_dist[self._opposite_route_index] - route_move_dist = new_accum_dist - prev_accum_dist + if len(new_opposite_wps) != len(self._opposite_sources): + # The topology has changed. Remap the new lanes to the sources + new_opposite_sources = [] for wp in new_opposite_wps: location = wp.transform.location new_source = None @@ -1576,23 +1556,18 @@ def _move_opposite_sources(self, prev_index, current_index): if new_source: new_source.wp = wp - new_opposite_sources.append(source) - self._opposite_sources.remove(source) + new_opposite_sources.append(new_source) + self._opposite_sources.remove(new_source) else: new_opposite_sources.append(Source(wp, [])) self._opposite_sources = new_opposite_sources else: - prev_accum_dist = self._accum_dist[prev_index] - current_accum_dist = self._accum_dist[current_index] - move_dist = current_accum_dist - prev_accum_dist - if move_dist <= 0: - return - + # The topology hasn't changed, move the distance backwards for source in self._opposite_sources: wp = source.wp if not self._is_junction(wp): - prev_wps = wp.previous(move_dist) + prev_wps = wp.previous(route_move_dist) if len(prev_wps) == 0: continue prev_wp = prev_wps[0] @@ -1602,14 +1577,19 @@ def _move_opposite_sources(self, prev_index, current_index): def _update_opposite_sources(self): """Checks the opposite actor sources to see if new actors have to be created""" + ego_speed = CarlaDataProvider.get_velocity(self._ego_actor) for source in self._opposite_sources: - # Cap the amount of alive actors - if len(source.actors) >= self._opposite_sources_max_actors: + if not source.active: + continue + + # Ending / starting lanes create issues as the lane width gradually decreases until reaching 0, + # where the lane starts / ends. Avoid spawning anything inside those parts with small lane width + if source.wp.lane_width < self._lane_width_threshold: continue # Calculate distance to the last created actor if len(source.actors) == 0: - distance = self._opposite_spawn_dist + 1 + distance = self._opposite_sources_dist + 1 else: actor_location = CarlaDataProvider.get_location(source.actors[-1]) if not actor_location: @@ -1621,153 +1601,549 @@ def _update_opposite_sources(self): actor = self._spawn_source_actor(source) if actor is None: continue - - self._tm.distance_to_leading_vehicle(actor, self._opposite_vehicle_dist) + self._tm.ignore_lights_percentage(actor, 100) + self._tm.ignore_signs_percentage(actor, 100) self._opposite_actors.append(actor) source.actors.append(actor) def _update_parameters(self): - """Changes the parameters depending on the blackboard variables and / or the speed of the ego""" - road_behavior_data = py_trees.blackboard.Blackboard().get("BA_RoadBehavior") - if road_behavior_data: - num_front_vehicles, num_back_vehicles, vehicle_dist, spawn_dist = road_behavior_data - if num_front_vehicles: + """ + Changes those parameters that have dynamic behaviors and / or that can be changed by external source. + This is done using py_trees' Blackboard variables and all behaviors should be at `background_manager.py`. + The blackboard variable is reset to None to avoid changing them back again next time. + """ + # Road behavior + road_behavior_data = py_trees.blackboard.Blackboard().get('BA_ChangeRoadBehavior') + if road_behavior_data is not None: + num_front_vehicles, num_back_vehicles, spawn_dist, extra_space = road_behavior_data + if num_front_vehicles is not None: self._road_front_vehicles = num_front_vehicles - if num_back_vehicles: + if num_back_vehicles is not None: self._road_back_vehicles = num_back_vehicles - if vehicle_dist: - self._road_vehicle_dist = vehicle_dist - if spawn_dist: + if spawn_dist is not None: self._road_spawn_dist = spawn_dist + if extra_space is not None: + self._road_extra_space = extra_space self._get_road_radius() - py_trees.blackboard.Blackboard().set("BA_RoadBehavior", None, True) + py_trees.blackboard.Blackboard().set('BA_ChangeRoadBehavior', None, True) - opposite_behavior_data = py_trees.blackboard.Blackboard().get("BA_OppositeBehavior") - if opposite_behavior_data: - source_dist, vehicle_dist, spawn_dist, max_actors = road_behavior_data - if source_dist: + # Opposite behavior + opposite_behavior_data = py_trees.blackboard.Blackboard().get('BA_ChangeOppositeBehavior') + if opposite_behavior_data is not None: + source_dist, spawn_dist, active = opposite_behavior_data + if source_dist is not None: if source_dist < self._junction_sources_dist: - print("WARNING: Opposite sources distance is lower than the junction ones. Ignoring it") + print('WARNING: Opposite sources distance is lower than the junction ones. Ignoring it') else: self._opposite_sources_dist = source_dist - if vehicle_dist: - self._opposite_vehicle_dist = vehicle_dist - if spawn_dist: + if spawn_dist is not None: self._opposite_spawn_dist = spawn_dist - if max_actors: - self._opposite_sources_max_actors = max_actors - py_trees.blackboard.Blackboard().set("BA_OppositeBehavior", None, True) - - junction_behavior_data = py_trees.blackboard.Blackboard().get("BA_JunctionBehavior") - if junction_behavior_data: - source_dist, vehicle_dist, spawn_dist, max_actors = road_behavior_data - if source_dist: + self._opposite_sources_dist = 2 * spawn_dist + if active is not None: + self._active_opposite_sources = active + for source in self._opposite_sources: + source.active = active + py_trees.blackboard.Blackboard().set('BA_ChangeOppositeBehavior', None, True) + + # Junction behavior + junction_behavior_data = py_trees.blackboard.Blackboard().get('BA_ChangeJunctionBehavior') + if junction_behavior_data is not None: + source_dist, spawn_dist, max_actors, source_perc = junction_behavior_data + if source_dist is not None: if source_dist > self._opposite_sources_dist: - print("WARNING: Junction sources distance is higher than the opposite ones. Ignoring it") + print('WARNING: Junction sources distance is higher than the opposite ones. Ignoring it') else: self._junction_sources_dist = source_dist - if vehicle_dist: - self._junction_vehicle_dist = vehicle_dist if spawn_dist: self._junction_spawn_dist = spawn_dist - if max_actors: + if max_actors is not None: self._junction_sources_max_actors = max_actors - py_trees.blackboard.Blackboard().set("BA_JunctionBehavior", None, True) - - break_duration = py_trees.blackboard.Blackboard().get("BA_Scenario2") - if break_duration: - if self._is_scenario_2_active: - print("WARNING: A break scenario was requested but another one is already being triggered.") - else: - self._activate_break_scenario = True - self._break_duration = break_duration - py_trees.blackboard.Blackboard().set("BA_Scenario2", None, True) - - crossing_dist = py_trees.blackboard.Blackboard().get("BA_Scenario4") - if crossing_dist: - self._is_scenario_4_active = True - self._crossing_dist = crossing_dist - py_trees.blackboard.Blackboard().set("BA_Scenario4", None, True) - - direction = py_trees.blackboard.Blackboard().get("BA_Scenario7") - if direction: - self._initialise_junction_scenario(direction, True, True, True) - py_trees.blackboard.Blackboard().set("BA_Scenario7", None, True) - direction = py_trees.blackboard.Blackboard().get("BA_Scenario8") - if direction: - self._initialise_junction_scenario(direction, True, True, True) - py_trees.blackboard.Blackboard().set("BA_Scenario8", None, True) - direction = py_trees.blackboard.Blackboard().get("BA_Scenario9") - if direction: - self._initialise_junction_scenario(direction, True, False, True) - py_trees.blackboard.Blackboard().set("BA_Scenario9", None, True) - direction = py_trees.blackboard.Blackboard().get("BA_Scenario10") - if direction: - self._initialise_junction_scenario(direction, False, False, False) - py_trees.blackboard.Blackboard().set("BA_Scenario10", None, True) + if source_perc is not None: + self._junction_source_perc = source_perc + py_trees.blackboard.Blackboard().set('BA_ChangeJunctionBehavior', None, True) + + # Max speed + max_speed = py_trees.blackboard.Blackboard().get('BA_SetMaxSpeed') + if max_speed is not None: + self._scenario_max_speed = max_speed + py_trees.blackboard.Blackboard().set('BA_SetMaxSpeed', None, True) + + # Stop front vehicles + stop_data = py_trees.blackboard.Blackboard().get('BA_StopFrontVehicles') + if stop_data is not None: + self._stop_road_front_vehicles() + py_trees.blackboard.Blackboard().set('BA_StopFrontVehicles', None, True) + + # Start front vehicles + start_data = py_trees.blackboard.Blackboard().get('BA_StartFrontVehicles') + if start_data is not None: + self._start_road_front_vehicles() + py_trees.blackboard.Blackboard().set("BA_StartFrontVehicles", None, True) + + # Stop back vehicles + stop_back_data = py_trees.blackboard.Blackboard().get('BA_StopBackVehicles') + if stop_back_data is not None: + self._stop_road_back_vehicles() + py_trees.blackboard.Blackboard().set('BA_StopBackVehicles', None, True) + + # Start back vehicles + start_back_data = py_trees.blackboard.Blackboard().get('BA_StartBackVehicles') + if start_back_data is not None: + self._start_road_back_vehicles() + py_trees.blackboard.Blackboard().set("BA_StartBackVehicles", None, True) + + # Leave space in front + leave_space_data = py_trees.blackboard.Blackboard().get('BA_LeaveSpaceInFront') + if leave_space_data is not None: + self._leave_space_in_front(leave_space_data) + py_trees.blackboard.Blackboard().set('BA_LeaveSpaceInFront', None, True) + + # Leave crosssing space + leave_crossing_space_data = py_trees.blackboard.Blackboard().get('BA_LeaveCrossingSpace') + if leave_crossing_space_data is not None: + self._leave_crossing_space(leave_crossing_space_data) + py_trees.blackboard.Blackboard().set('BA_LeaveCrossingSpace', None, True) + + # Remove road lane + remove_road_lane_data = py_trees.blackboard.Blackboard().get('BA_RemoveRoadLane') + if remove_road_lane_data is not None: + self._remove_road_lane(remove_road_lane_data) + py_trees.blackboard.Blackboard().set('BA_RemoveRoadLane', None, True) + + # Readd road lane + readd_road_lane_data = py_trees.blackboard.Blackboard().get('BA_ReAddRoadLane') + if readd_road_lane_data is not None: + self._readd_road_lane(readd_road_lane_data) + py_trees.blackboard.Blackboard().set('BA_ReAddRoadLane', None, True) + + # Adapt the BA to the junction scenario + junction_scenario_data = py_trees.blackboard.Blackboard().get('BA_HandleJunctionScenario') + if junction_scenario_data is not None: + self._handle_junction_scenario(junction_scenario_data) + py_trees.blackboard.Blackboard().set("BA_HandleJunctionScenario", None, True) + + # Switch route sources + switch_sources_data = py_trees.blackboard.Blackboard().get('BA_SwitchRouteSources') + if switch_sources_data is not None: + self._switch_route_sources(switch_sources_data) + py_trees.blackboard.Blackboard().set("BA_SwitchRouteSources", None, True) self._compute_parameters() def _compute_parameters(self): """Computes the parameters that are dependent on the speed of the ego. """ ego_speed = CarlaDataProvider.get_velocity(self._ego_actor) + self._min_radius = self._base_min_radius + self._radius_increase_ratio * ego_speed + self._road_extra_space + self._max_radius = self._base_max_radius + self._radius_increase_ratio * ego_speed + self._road_extra_space + self._detection_dist = self._base_junction_detection + self._detection_ratio * ego_speed - # As the vehicles don't move if the agent doesn't, some agents might get blocked forever. - # Partially avoid this by adding an extra distance to the radius when the vehicle is stopped - # in the middle of the road and unaffected by any object such as traffic lights or stops. - if ego_speed == 0 \ - and not self._is_scenario_2_active \ - and not self._ego_actor.is_at_traffic_light() \ - and len(self._active_junctions) <= 0: - self._extra_radius = min(self._extra_radius + self._extra_radius_increase_ratio, self._max_extra_radius) - - # At all cases, reduce it if the agent is moving - if ego_speed > 0 and self._extra_radius > 0: - self._extra_radius = max(self._extra_radius - self._extra_radius_increase_ratio, 0) - - self._min_radius = self._base_min_radius + self._radius_increase_ratio * ego_speed + self._extra_radius - self._max_radius = self._base_max_radius + self._radius_increase_ratio * ego_speed + self._extra_radius - self._junction_detection_dist = self._max_radius - - def _manage_break_scenario(self): - """ - Manages the break scenario, where all road vehicles in front of the ego suddenly stop, - wait for a bit, and start moving again. This will never trigger unless done so from outside. - """ - if self._is_scenario_2_active: - self._next_scenario_time -= self._world.get_snapshot().timestamp.delta_seconds - if self._next_scenario_time <= 0: - for actor in self._scenario_2_actors: - self._tm.vehicle_percentage_speed_difference(actor, 0) + def _stop_road_front_vehicles(self): + """ + Stops all road vehicles in front of the ego. Use `_start_road_front_vehicles` to make them move again. + """ + for lane in self._road_dict: + for actor in self._road_dict[lane].actors: + location = CarlaDataProvider.get_location(actor) + if location and not self._is_location_behind_ego(location): + self._scenario_stopped_actors.append(actor) + self._actors_speed_perc[actor] = 0 + self._tm.update_vehicle_lights(actor, False) lights = actor.get_light_state() - lights &= ~carla.VehicleLightState.Brake + lights |= carla.VehicleLightState.Brake actor.set_light_state(carla.VehicleLightState(lights)) - self._scenario_2_actors = [] - self._is_scenario_2_active = False + def _start_road_front_vehicles(self): + """ + Restarts all road vehicles stopped by `_stop_road_front_vehicles`. + """ + for actor in self._scenario_stopped_actors: + self._actors_speed_perc[actor] = 100 + self._tm.update_vehicle_lights(actor, True) + lights = actor.get_light_state() + lights &= ~carla.VehicleLightState.Brake + actor.set_light_state(carla.VehicleLightState(lights)) + self._scenario_stopped_actors = [] + + def _stop_road_back_vehicles(self): + """ + Stops all road vehicles behind the ego. Use `_start_road_back_vehicles` to make them move again. + """ + for lane in self._road_dict: + for actor in self._road_dict[lane].actors: + location = CarlaDataProvider.get_location(actor) + if location and self._is_location_behind_ego(location): + self._actors_speed_perc[actor] = 0 + self._scenario_stopped_back_actors.append(actor) + + def _start_road_back_vehicles(self): + """ + Restarts all road vehicles stopped by `_stop_road_back_vehicles`. + """ + for actor in self._scenario_stopped_back_actors: + self._actors_speed_perc[actor] = 100 + self._scenario_stopped_back_actors = [] + + def _move_actors_forward(self, actors, space): + """Teleports the actors forward a set distance""" + for actor in list(actors): + location = CarlaDataProvider.get_location(actor) + if not location: + continue + + actor_wp = self._map.get_waypoint(location) + new_actor_wps = actor_wp.next(space) + if len(new_actor_wps) > 0: + new_transform = new_actor_wps[0].transform + new_transform.location.z += 0.2 + actor.set_transform(new_transform) + else: + self._destroy_actor(actor) + + def _switch_route_sources(self, enabled): + """ + Disables all sources that are part of the ego's route + """ + self._active_road_sources = enabled + for lane in self._road_dict: + self._road_dict[lane].active = enabled + + for junction in self._active_junctions: + for source in junction.entry_sources: + if get_lane_key(source.entry_lane_wp) in junction.route_entry_keys: + source.active = enabled + + def _leave_space_in_front(self, space): + """Teleports all the vehicles in front of the ego forward""" + if self._ego_key not in self._road_dict: + return + + if self._active_junctions: + return + + front_actors = [] + min_distance = float('inf') + for actor in self._road_dict[self._ego_key].actors: + location = CarlaDataProvider.get_location(actor) + if not location or self._is_location_behind_ego(location): + continue + + front_actors.append(actor) + distance = location.distance(self._ego_wp.transform.location) + if distance < min_distance: + min_distance = distance + + step = space - min_distance + if step > 0: # Only move them if needed and only the minimum required distance + self._move_actors_forward(front_actors, step) + + def _leave_crossing_space(self, collision_wp): + """Removes all vehicle in the middle of crossing trajectory and stops the nearby ones""" + destruction_dist = 20 + stop_dist = 30 + + opposite_wp = collision_wp.get_left_lane() + if not opposite_wp: + return # Nothing else to do + opposite_loc = opposite_wp.transform.location + + for actor in list(self._opposite_actors): + location = actor.get_location() + if not location: + continue + + collision_dist = location.distance(opposite_loc) + if collision_dist < destruction_dist: + self._destroy_actor(actor) + elif collision_dist < stop_dist: + actor.set_target_velocity(carla.Vector3D()) + + def _remove_road_lane(self, lane_wp): + """Removes a road lane""" + self._scenario_removed_lane = True + self._scenario_remove_lane_offset = 0 + lane_key = get_lane_key(lane_wp) + if lane_key not in list(self._road_dict): + print(f"WARNING: Couldn't find the lane to be removed, '{lane_key}' isn't part of the road behavior") + return + + self._road_dict[lane_key].active = False + for actor in list(self._road_dict[lane_key].actors): + self._destroy_actor(actor) + self._road_dict.pop(lane_key, None) + + def _readd_road_lane(self, lane_offset): + """Adds a ego road lane. This is expected to be used after having previously removed such lane""" + # Check that the ego hasn't moved close to a junction, where we don't want to reinitialize the lane + if not self._scenario_removed_lane: + return + + lane_offset += self._scenario_remove_lane_offset + + if lane_offset == 0: + add_lane_wp = self._ego_wp + add_lane_key = self._ego_key + else: + side_wp = self._ego_wp + for _ in range(abs(lane_offset)): + side_wp = side_wp.get_right_lane() if lane_offset > 0 else side_wp.get_left_lane() + if not side_wp: + print(f"WARNING: Couldn't find a lane with the desired offset") + return + + add_lane_wp = side_wp + add_lane_key = get_lane_key(side_wp) + + if add_lane_key in list(self._road_dict): + print(f"WARNING: Couldn't add a lane {add_lane_key} as it is already part of the road") + return + + ego_speed = CarlaDataProvider.get_velocity(self._ego_actor) + spawn_dist = self._road_spawn_dist + 2 * ego_speed + + spawn_wps = [] + + next_wp = add_lane_wp + for _ in range(self._road_front_vehicles): + next_wps = next_wp.next(spawn_dist) + if len(next_wps) != 1 or self._is_junction(next_wps[0]): + break # Stop when there's no next or found a junction + next_wp = next_wps[0] + spawn_wps.insert(0, next_wp) + + source_dist = 0 + prev_wp = add_lane_wp + for _ in range(self._road_back_vehicles): + prev_wps = prev_wp.previous(spawn_dist) + if len(prev_wps) != 1 or self._is_junction(prev_wps[0]): + break # Stop when there's no next or found a junction + prev_wp = prev_wps[0] + spawn_wps.append(prev_wp) + source_dist += spawn_dist + + actors = [] + for spawn_wp in spawn_wps: + actor = self._spawn_actor(spawn_wp) + if not actor: + continue + actor.set_target_velocity(spawn_wp.transform.get_forward_vector() * ego_speed) + actors.append(actor) - elif self._activate_break_scenario: - for actor in self._road_actors: + self._road_dict[add_lane_key] = Source(prev_wp, actors, active=self._active_road_sources) + + self._scenario_removed_lane = False + self._scenario_remove_lane_offset = 0 + + def _handle_junction_scenario(self, junction_data): + """ + Adapts the BA to the junction scenario by clearing the junction, + its entries, exits, and by extending the road exit to add more space + """ + clear_junction, clear_ego_entry, remove_entries, remove_exits, stop_entries, extend_road_exit = junction_data + + if clear_junction: + self._clear_junction_middle() + + if clear_ego_entry: + self._clear_ego_entry() + + if remove_entries: + self._remove_junction_entries(remove_entries) + + if remove_exits: + self._remove_junction_exits(remove_exits) + + if stop_entries: + self._stop_non_ego_route_entries() + + if extend_road_exit: + self._extent_road_exit_space(extend_road_exit) + + self._scenario_removed_lane = False + self._scenario_remove_lane_offset = 0 + + def _clear_junction_middle(self): + """Clears the junction, and all subsequent actors that enter it""" + if self._active_junctions: + junction = self._active_junctions[0] + actor_dict = junction.actor_dict + for actor in list(actor_dict): + if actor_dict[actor]['state'] == JUNCTION_MIDDLE: + self._destroy_actor(actor) + junction.clear_middle = True + + elif self._junctions: + self._junctions[0].clear_middle = True + + def _clear_ego_entry(self): + """ + Remove all actors in front of the vehicle. + """ + def handle_actors(actor_list): + for actor in list(actor_list): location = CarlaDataProvider.get_location(actor) if location and not self._is_location_behind_ego(location): - self._scenario_2_actors.append(actor) - self._tm.vehicle_percentage_speed_difference(actor, 100) - lights = actor.get_light_state() - lights |= carla.VehicleLightState.Brake - actor.set_light_state(carla.VehicleLightState(lights)) + self._destroy_actor(actor) + + # This will make the actors behind never overtake the ego + self._scenario_junction_entry = True - self._is_scenario_2_active = True - self._activate_break_scenario = False - self._next_scenario_time = self._break_duration + if self._active_junctions: + for source in self._active_junctions[0].entry_sources: + handle_actors(source.actors) + else: + for lane_key in list(self._road_dict): + source = self._road_dict[lane_key] + handle_actors(source.actors) + source.active = False + + def _remove_junction_entries(self, wps): + """Removes a list of entries of the closest junction (or marks them so that they aren't spawned)""" + if len(self._active_junctions) > 0: + junction = self._active_junctions[0] + elif len(self._junctions) > 0: + junction = self._junctions[0] + else: + return + + for wp in wps: + mapped_key = None + next_wp = wp + while not self._is_junction(next_wp): + lane_key = get_lane_key(next_wp) + if lane_key in junction.entry_lane_keys: + mapped_key = lane_key + break + next_wps = next_wp.next(1) + if not next_wps: + break + next_wp = next_wps[0] + + if not mapped_key: + print("WARNING: Couldn't find the asked entry to be removed") + continue + + if len(self._active_junctions) > 0: + for source in junction.entry_sources: + if get_lane_key(source.wp) == mapped_key: + for actor in list(source.actors): + self._destroy_actor(actor) + source.active = False + else: + junction.inactive_entry_keys.append(mapped_key) + + def _remove_junction_exits(self, wps): + """Removes a list of exit of the closest junction (or marks them so that they aren't spawned)""" + if len(self._active_junctions) > 0: + junction = self._active_junctions[0] + elif len(self._junctions) > 0: + junction = self._junctions[0] + else: + return + + for wp in wps: + + mapped_key = None + prev_wp = wp + while not self._is_junction(prev_wp): + lane_key = get_lane_key(prev_wp) + if lane_key in junction.exit_dict: + mapped_key = lane_key + break + prev_wps = prev_wp.next(1) + if not prev_wps: + break + prev_wp = prev_wps[0] + + if not mapped_key: + print("WARNING: Couldn't find the asked exit to be removed") + continue + + if len(self._active_junctions) > 0: + for actor in list(junction.exit_dict[mapped_key]['actors']): + self._destroy_actor(actor) + else: + junction.inactive_exit_keys.append(mapped_key) + + def _stop_non_ego_route_entries(self): + """Clears the junction, and all subsequent actors that enter it""" + if self._active_junctions: + + # Remove all actors in the middle + junction = self._active_junctions[0] + actor_dict = junction.actor_dict + + entry_sources = junction.entry_sources + route_entry_keys = junction.route_entry_keys + for source in entry_sources: + if get_lane_key(source.entry_lane_wp) not in route_entry_keys: + for actor in source.actors: + if actor_dict[actor]['state'] == JUNCTION_ENTRY: + self._actors_speed_perc[actor] = 0 + + elif self._junctions: + self._junctions[0].stop_non_route_entries = True + + def _extent_road_exit_space(self, space): + """Increases the space left by the exit vehicles at a specific road""" + if len(self._active_junctions) > 0: + junction = self._active_junctions[0] + elif len(self._junctions) > 0: + junction = self._junctions[0] + else: + return + + route_lane_keys = junction.route_exit_keys + + for exit_lane_key in route_lane_keys: + junction.exit_dict[exit_lane_key]['max_distance'] += space + actors = junction.exit_dict[exit_lane_key]['actors'] + self._move_actors_forward(actors, space) + for actor in actors: + if junction.actor_dict[actor]['state'] == JUNCTION_EXIT_ROAD: + self._actors_speed_perc[actor] = 100 + junction.actor_dict[actor]['state'] = JUNCTION_EXIT ############################# ## Actor functions ## ############################# + def _initialise_actor(self, actor): + """ + Save the actor into the needed structures, disable its lane changes and set the leading distance. + """ + self._tm.auto_lane_change(actor, self._vehicle_lane_change) + self._tm.update_vehicle_lights(actor, self._vehicle_lights) + self._tm.distance_to_leading_vehicle(actor, self._vehicle_leading_distance) + self._tm.vehicle_lane_offset(actor, self._vehicle_offset) + self._all_actors.append(actor) + + def _spawn_actor(self, spawn_wp, ego_dist=0): + """Spawns an actor""" + ego_location = CarlaDataProvider.get_location(self._ego_actor) + if ego_location.distance(spawn_wp.transform.location) < ego_dist: + return None + + spawn_transform = carla.Transform( + spawn_wp.transform.location + carla.Location(z=self._spawn_vertical_shift), + spawn_wp.transform.rotation + ) + + actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', spawn_transform, 'background', True, + attribute_filter={'base_type': 'car', 'has_lights': True}, tick=False + ) + + if not actor: + return actor + self._initialise_actor(actor) + return actor - def _spawn_actors(self, spawn_wps): + def _spawn_actors(self, spawn_wps, ego_dist=0): """Spawns several actors in batch""" spawn_transforms = [] + ego_location = CarlaDataProvider.get_location(self._ego_actor) for wp in spawn_wps: + if ego_location.distance(wp.transform.location) < ego_dist: + continue spawn_transforms.append( carla.Transform(wp.transform.location + carla.Location(z=self._spawn_vertical_shift), wp.transform.rotation) @@ -1775,22 +2151,17 @@ def _spawn_actors(self, spawn_wps): actors = CarlaDataProvider.request_new_batch_actors( 'vehicle.*', len(spawn_transforms), spawn_transforms, True, False, 'background', - safe_blueprint=True, tick=False) + attribute_filter=self._attribute_filter, tick=False) if not actors: return actors for actor in actors: - self._tm.auto_lane_change(actor, False) - - if self._night_mode: - for actor in actors: - actor.set_light_state(carla.VehicleLightState( - carla.VehicleLightState.Position | carla.VehicleLightState.LowBeam)) + self._initialise_actor(actor) return actors - def _spawn_source_actor(self, source, ego_dist=0): + def _spawn_source_actor(self, source, ego_dist=20): """Given a source, spawns an actor at that source""" ego_location = CarlaDataProvider.get_location(self._ego_actor) source_transform = source.wp.transform @@ -1803,16 +2174,12 @@ def _spawn_source_actor(self, source, ego_dist=0): ) actor = CarlaDataProvider.request_new_actor( 'vehicle.*', new_transform, rolename='background', - autopilot=True, random_location=False, safe_blueprint=True, tick=False) + autopilot=True, random_location=False, attribute_filter=self._attribute_filter, tick=False) if not actor: return actor - self._tm.auto_lane_change(actor, False) - if self._night_mode: - actor.set_light_state(carla.VehicleLightState( - carla.VehicleLightState.Position | carla.VehicleLightState.LowBeam)) - + self._initialise_actor(actor) return actor def _is_location_behind_ego(self, location): @@ -1820,155 +2187,181 @@ def _is_location_behind_ego(self, location): ego_transform = self._route[self._route_index].transform ego_heading = ego_transform.get_forward_vector() ego_actor_vec = location - ego_transform.location - if ego_heading.x * ego_actor_vec.x + ego_heading.y * ego_actor_vec.y < - 0.17: # 100º + if ego_heading.dot(ego_actor_vec) < - 0.17: # 100º return True return False - def _get_ego_route_lane_key(self, route_wp): - """ - Gets the route lane key of the ego. This corresponds to the same lane if the ego is driving normally, - but if is is going in opposite direction, the route's leftmost one is chosen instead - """ - location = CarlaDataProvider.get_location(self._ego_actor) - ego_true_wp = self._map.get_waypoint(location) - if get_road_key(ego_true_wp) != get_road_key(route_wp): - # Just return the default value as two different roads are being compared. - # This might happen for when moving to a new road and should be fixed next frame - return get_lane_key(route_wp) - - yaw_diff = (ego_true_wp.transform.rotation.yaw - route_wp.transform.rotation.yaw) % 360 - if yaw_diff < 90 or yaw_diff > 270: - return get_lane_key(ego_true_wp) - else: - # Get the first lane of the opposite direction - leftmost_wp = route_wp - while True: - possible_left_wp = leftmost_wp.get_left_lane() - if possible_left_wp is None or possible_left_wp.lane_id * leftmost_wp.lane_id < 0: - break - leftmost_wp = possible_left_wp - return get_lane_key(leftmost_wp) - - def _update_road_actors(self, prev_ego_index, current_ego_index): + def _update_road_actors(self): """ Dynamically controls the actor speed in front of the ego. Not applied to those behind it so that they can catch up it """ - route_wp = self._route[current_ego_index] - scenario_actors = self._scenario_4_actors + self._scenario_2_actors - for actor in self._road_actors: - location = CarlaDataProvider.get_location(actor) - if not location: - continue - if self.debug: - back_actor = False - for lane in self._road_back_actors: - if actor in self._road_back_actors[lane]: - back_actor = True - if back_actor: - draw_string(self._world, location, 'R(B)', 'road', False) - else: - draw_string(self._world, location, 'R(F)', 'road', False) - if actor in scenario_actors: - continue - if self._is_location_behind_ego(location): - continue + # Updates their speed + scenario_actors = self._scenario_stopped_actors + self._scenario_stopped_back_actors + for lane_key in self._road_dict: + for i, actor in enumerate(self._road_dict[lane_key].actors): + location = CarlaDataProvider.get_location(actor) + if not location: + continue + if self.debug: + string = 'R_' + string += 'B' if self._is_location_behind_ego(location) else 'F' + string += '_(' + str(i) + ')' + string += '_[' + lane_key + ']' + draw_string(self._world, location, string, DEBUG_ROAD, False) + + # Actors part of scenarios are their own category, ignore them + if actor in scenario_actors: + continue - distance = location.distance(route_wp.transform.location) - speed_red = (distance - self._min_radius) / (self._max_radius - self._min_radius) * 100 - speed_red = np.clip(speed_red, 0, 100) - self._tm.vehicle_percentage_speed_difference(actor, speed_red) + # TODO: Lane changes are weird with the TM, so just stop them + actor_wp = self._map.get_waypoint(location) + if actor_wp.lane_width < self._lane_width_threshold: + + # Ensure only ending lanes are affected. not sure if it is needed though + next_wps = actor_wp.next(0.5) + if next_wps and next_wps[0].lane_width < actor_wp.lane_width: + actor.set_target_velocity(carla.Vector3D(0, 0, 0)) + self._actors_speed_perc[actor] = 0 + lights = actor.get_light_state() + lights |= carla.VehicleLightState.RightBlinker + lights |= carla.VehicleLightState.LeftBlinker + lights |= carla.VehicleLightState.Position + actor.set_light_state(carla.VehicleLightState(lights)) + actor.set_autopilot(False, self._tm_port) + continue - # Check how the vehicles behind are - self._check_back_vehicles(prev_ego_index, current_ego_index) + self._set_road_actor_speed(location, actor) - def _check_back_vehicles(self, prev_route_index, current_route_index): + def _set_road_actor_speed(self, location, actor, multiplier=1): """ - Checks if any of the vehicles that should be behind the ego are in front, updating the road radius. - This is done by monitoring the closest lane key to the ego that is part of the route, - and needs some remaping when the ego enters a new road + Changes the speed of the vehicle depending on its distance to the ego. + - Front vehicles: Gradually reduces the speed the further they are. + - Back vehicles: Gradually reduces the speed the further they are to help them catch up to the ego. + - Junction scenario behavior: Don't let vehicles behind the ego surpass it. """ - route_wp = self._route[current_route_index] + distance = location.distance(self._ego_wp.transform.location) + if not self._is_location_behind_ego(location): + percentage = (self._max_radius - distance) / (self._max_radius - self._min_radius) * 100 + percentage *= multiplier + percentage = max(min(percentage, 100), 0) + elif not self._scenario_junction_entry: + percentage = distance / (self._max_radius - self._min_radius) * 100 + 100 + percentage = max(min(percentage, 200), 0) + else: + ego_speed = CarlaDataProvider.get_velocity(self._ego_actor) + base_percentage = ego_speed / self._ego_target_speed * 100 + true_distance = distance - self._scenario_junction_entry_distance + percentage = true_distance / (self._max_radius - self._min_radius) * 100 + base_percentage + percentage = max(min(percentage, 100), 0) + + self._actors_speed_perc[actor] = percentage + + def _monitor_road_changes(self, prev_route_index): + """ + Checks if the ego changes road, remapping the route keys. + """ + def get_lane_waypoints(reference_wp, index=0): + if not reference_wp.is_junction: + wps = get_same_dir_lanes(reference_wp) + else: # Handle fake junction by using its entry / exit wps + wps = [] + for junction_wps in reference_wp.get_junction().get_waypoints(carla.LaneType.Driving): + if get_road_key(junction_wps[index]) == get_road_key(reference_wp): + wps.append(junction_wps[index]) + return wps + + def get_wp_pairs(old_wps_, new_wps_, dist): + wp_pairs = [] + + unmapped_wps = new_wps_ + for old_wp_ in old_wps_: + mapped = False + location = old_wp_.transform.location + for new_wp_ in unmapped_wps: + if location.distance(new_wp_.transform.location) < dist: + unmapped_wps.remove(new_wp_) + wp_pairs.append([old_wp_, new_wp_]) + mapped = True + break + + if not mapped: + wp_pairs.append([old_wp_, None]) + + for unmapped_wp in unmapped_wps: + wp_pairs.append([None, unmapped_wp]) + + return wp_pairs + + def is_remapping_needed(current_wp, prev_wp): + """The road dict mapping is needed if """ + # If the ego just exitted a junction, remap isn't needed + if self._is_junction(prev_wp): + return False + + # If the road changes, remap + if get_road_key(prev_wp) != get_road_key(current_wp): + return True + + # Some roads have starting / ending lanes in the middle. Remap if that is detected + prev_wps = get_same_dir_lanes(prev_wp) + current_wps = get_same_dir_lanes(current_wp) + if len(prev_wps) != len(current_wps): + return True + + return False + + def is_road_dict_unchanging(wp_pairs): + """Sometimes 'monitor_topology_changes' has already done the necessary changes""" + road_dict_keys = list(self._road_dict) + if len(wp_pairs) != len(road_dict_keys): + return False + + for _, new_wp in wp_pairs: + if get_lane_key(new_wp) not in road_dict_keys: + return False + return True + + if prev_route_index == self._route_index: + return + route_wp = self._route[self._route_index] prev_route_wp = self._route[prev_route_index] - check_dist = 1.1 * route_wp.transform.location.distance(prev_route_wp.transform.location) - if prev_route_index != current_route_index: - road_change = self._found_a_road_change(prev_route_index, current_route_index, ignore_false_junctions=False) - if not self._is_junction(prev_route_wp) and road_change: - # Get all the wps of the new road - if not route_wp.is_junction: - new_wps = get_same_dir_lanes(route_wp) - else: # Entering a false junction - new_wps = [] - for enter_wp, _ in route_wp.get_junction().get_waypoints(carla.LaneType.Driving): - if get_road_key(enter_wp) == get_road_key(route_wp): - new_wps.append(enter_wp) - - # Get all the wps of the old road - if not prev_route_wp.is_junction: - old_wps = get_same_dir_lanes(prev_route_wp) - else: # Exitting a false junction - old_wps = [] - for _, exit_wp in prev_route_wp.get_junction().get_waypoints(carla.LaneType.Driving): - if get_road_key(exit_wp) == get_road_key(prev_route_wp): - old_wps.append(exit_wp) - - # Map the new lanes to the old ones - mapped_keys = {} - unmapped_wps = new_wps - for old_wp in list(old_wps): - location = old_wp.transform.location - mapped_wp = None - for new_wp in unmapped_wps: - if location.distance(new_wp.transform.location) < check_dist: - mapped_wp = new_wp - break - if mapped_wp: - unmapped_wps.remove(mapped_wp) - mapped_keys[get_lane_key(old_wp)] = get_lane_key(mapped_wp) - - # Remake the road back actors dictionary - new_road_back_actors = {} - for lane_key in self._road_back_actors: - if lane_key not in mapped_keys: - continue # A lane ended at that road - new_lane_key = mapped_keys[lane_key] - new_road_back_actors[new_lane_key] = self._road_back_actors[lane_key] - - # For the active sources, change the mapped key to the new road keys - for source in self._road_sources: - if source.mapped_key in mapped_keys: - source.mapped_key = mapped_keys[source.mapped_key] - self._road_back_actors = new_road_back_actors - - # New lanes, add new sources - for unmapped_wp in unmapped_wps: - source_wps = unmapped_wp.next(self._road_new_sources_dist) - if len(source_wps) != 1: - continue - new_source = Source(source_wps[0], []) - self._road_sources.append(new_source) - self._road_back_actors[new_source.mapped_key] = [] + if not is_remapping_needed(route_wp, prev_route_wp): + return - if not self._road_ego_key in mapped_keys: - # Return the default. This might happen when the route lane ends and should be fixed next frame - self._road_ego_key = get_lane_key(route_wp) - else: - self._road_ego_key = mapped_keys[self._road_ego_key] - else: - self._road_ego_key = self._get_ego_route_lane_key(route_wp) + new_road_dict = {} + old_wps = get_lane_waypoints(prev_route_wp, 1) + new_wps = get_lane_waypoints(route_wp, 0) + check_dist = 1.1 * route_wp.transform.location.distance(prev_route_wp.transform.location) + wp_pairs = get_wp_pairs(old_wps, new_wps, check_dist) + # TODO: These pairs are sometimes wrong as some fake intersections have overlapping lanes (highway entries) - # Get the amount of vehicles in front of the ego - if not self._road_ego_key in self._road_back_actors: + if is_road_dict_unchanging(wp_pairs): return - self._road_extra_front_actors = 0 - for actor in self._road_back_actors[self._road_ego_key]: - if not self._is_location_behind_ego(actor.get_location()): - self._road_extra_front_actors += 1 + for old_wp, new_wp in wp_pairs: + old_key = get_lane_key(old_wp) + new_key = get_lane_key(new_wp) - self._get_road_radius() - self._compute_parameters() + # Lane has ended / started, no need to remap it + if not new_wp or not old_wp: + continue + + if self.debug: + draw_arrow(self._world, old_wp.transform.location, new_wp.transform.location, DEBUG_MEDIUM, DEBUG_ROAD, True) + + # Check that the lane is part of the road dictionary + if old_key in list(self._road_dict): + new_road_dict[new_key] = self._road_dict[old_key] + self._road_dict.pop(old_key) + continue + + # Add the rest of the unmapped road sources, mainly those created forward at lanes that are starting + for lane_key in list(self._road_dict): + new_road_dict[lane_key] = self._road_dict.pop(lane_key) + + self._road_dict = new_road_dict def _update_junction_actors(self): """ @@ -1986,15 +2379,21 @@ def _update_junction_actors(self): route_oppo_keys = junction.opposite_entry_keys + junction.opposite_exit_keys for wp in junction.entry_wps + junction.exit_wps: if get_lane_key(wp) in route_keys: - draw_point(self._world, wp.transform.location, 'medium', 'road', False) + draw_point(self._world, wp.transform.location, DEBUG_MEDIUM, DEBUG_ROAD, False) elif get_lane_key(wp) in route_oppo_keys: - draw_point(self._world, wp.transform.location, 'medium', 'opposite', False) + draw_point(self._world, wp.transform.location, DEBUG_MEDIUM, DEBUG_OPPOSITE, False) else: - draw_point(self._world, wp.transform.location, 'medium', 'junction', False) + draw_point(self._world, wp.transform.location, DEBUG_MEDIUM, DEBUG_JUNCTION, False) actor_dict = junction.actor_dict exit_dict = junction.exit_dict - remove_middle = junction.scenario_info['remove_middle'] + + scenario_entry_actor_ids = [] + if self._scenario_junction_entry: + for source in junction.entry_sources: + if get_lane_key(source.wp) in junction.route_entry_keys: + scenario_entry_actor_ids.extend([x.id for x in source.actors]) + for actor in list(actor_dict): if actor not in actor_dict: continue # Actor was removed during the loop @@ -2004,20 +2403,24 @@ def _update_junction_actors(self): state, exit_lane_key, _ = actor_dict[actor].values() if self.debug: - string = 'J' + str(i+1) + "_" + state[9:11] - draw_string(self._world, location, string, self._ego_state, False) + string = 'J' + str(i+1) + '_' + state[:2] + draw_string(self._world, location, string, DEBUG_JUNCTION, False) + + # Special scenario actors. Treat them as road actors + if actor.id in scenario_entry_actor_ids: + self._set_road_actor_speed(location, actor) # Monitor its entry - if state == 'junction_entry': + elif state == JUNCTION_ENTRY: actor_wp = self._map.get_waypoint(location) - if self._is_junction(actor_wp) and junction.contains(actor_wp.get_junction()): - if remove_middle: + if self._is_junction(actor_wp) and junction.contains_wp(actor_wp): + if junction.clear_middle: self._destroy_actor(actor) # Don't clutter the junction if a junction scenario is active continue - actor_dict[actor]['state'] = 'junction_middle' + actor_dict[actor]['state'] = JUNCTION_MIDDLE # Monitor its exit and destroy an actor if needed - elif state == 'junction_middle': + elif state == JUNCTION_MIDDLE: actor_wp = self._map.get_waypoint(location) actor_lane_key = get_lane_key(actor_wp) if not self._is_junction(actor_wp) and actor_lane_key in exit_dict: @@ -2038,7 +2441,7 @@ def _update_junction_actors(self): else: # Check the lane capacity exit_dict[actor_lane_key]['ref_wp'] = actor_wp - actor_dict[actor]['state'] = 'junction_exit' + actor_dict[actor]['state'] = JUNCTION_EXIT actor_dict[actor]['exit_lane_key'] = actor_lane_key actors = exit_dict[actor_lane_key]['actors'] @@ -2046,54 +2449,78 @@ def _update_junction_actors(self): self._destroy_actor(actors[0]) # This is always the front most vehicle actors.append(actor) - # Deactivate them when far from the junction - elif state == 'junction_exit': + # Change them to "road mode" when far enough from the junction + elif state == JUNCTION_EXIT: distance = location.distance(exit_dict[exit_lane_key]['ref_wp'].transform.location) if distance > exit_dict[exit_lane_key]['max_distance']: - self._tm.vehicle_percentage_speed_difference(actor, 100) - actor_dict[actor]['state'] = 'junction_inactive' + if exit_lane_key in junction.route_exit_keys: + actor_dict[actor]['state'] = JUNCTION_EXIT_ROAD + else: + self._actors_speed_perc[actor] = 0 + actor_dict[actor]['state'] = JUNCTION_EXIT_INACTIVE + + # Set them ready to move so that the ego can smoothly cross the junction + elif state == JUNCTION_EXIT_ROAD: + self._set_road_actor_speed(location, actor, multiplier=1.5) + pass - # Wait for something to happen - elif state == 'junction_inactive': + # Wait + elif state == JUNCTION_EXIT_INACTIVE: pass - def _update_opposite_actors(self, ref_transform): + def _update_opposite_actors(self): """ Updates the opposite actors. This involves tracking their position, - removing if too far behind the ego + removing them if too far behind the ego. """ - max_dist = max(self._opposite_removal_dist, self._opposite_spawn_dist) + opposite_dist = max(self._opposite_sources_dist, self._opposite_spawn_dist) for actor in list(self._opposite_actors): location = CarlaDataProvider.get_location(actor) if not location: continue if self.debug: - draw_string(self._world, location, 'O', 'opposite', False) - distance = location.distance(ref_transform.location) - if distance > max_dist and self._is_location_behind_ego(location): + draw_string(self._world, location, 'O', DEBUG_OPPOSITE, False) + + distance = location.distance(self._ego_wp.transform.location) + if distance > opposite_dist and self._is_location_behind_ego(location): self._destroy_actor(actor) + continue + + # Ending / starting lanes create issues as the lane width gradually decreases until reaching 0, + # where the lane starts / ends. Set their speed to 0, and they'll eventually dissapear. + actor_wp = self._map.get_waypoint(location) + if actor_wp.lane_width < self._lane_width_threshold: + self._actors_speed_perc[actor] = 0 + + def _set_actors_speed(self): + """ + Sets the speed of all the BA actors, using the ego's target speed as reference. + This avoids issues with the speed limits, as newly created actors don't have that information + """ + for actor, percentage in self._actors_speed_perc.items(): + speed = self._ego_target_speed * percentage / 100 + if self._scenario_max_speed: + speed = min(speed, self._scenario_max_speed) + + # TODO: Fix very high speed traffic + speed = min(speed, 90) + self._tm.set_desired_speed(actor, speed) def _remove_actor_info(self, actor): """Removes all the references of the actor""" - if actor in self._road_actors: - self._road_actors.remove(actor) - if actor in self._opposite_actors: - self._opposite_actors.remove(actor) - if actor in self._scenario_2_actors: - self._scenario_2_actors.remove(actor) - if actor in self._scenario_4_actors: - self._scenario_4_actors.remove(actor) - - for road_source in self._road_sources: - if actor in road_source.actors: - road_source.actors.remove(actor) - break - for lane in self._road_back_actors: - if actor in self._road_back_actors[lane]: - self._road_back_actors[lane].remove(actor) + for lane in self._road_dict: + if actor in self._road_dict[lane].actors: + self._road_dict[lane].actors.remove(actor) break + if actor in self._opposite_actors: + self._opposite_actors.remove(actor) + if actor in self._scenario_stopped_actors: + self._scenario_stopped_actors.remove(actor) + if actor in self._scenario_stopped_back_actors: + self._scenario_stopped_back_actors.remove(actor) + for opposite_source in self._opposite_sources: if actor in opposite_source.actors: opposite_source.actors.remove(actor) @@ -2102,11 +2529,6 @@ def _remove_actor_info(self, actor): for junction in self._active_junctions: junction.actor_dict.pop(actor, None) - for exit_source in junction.exit_sources: - if actor in exit_source.actors: - exit_source.actors.remove(actor) - break - for entry_source in junction.entry_sources: if actor in entry_source.actors: entry_source.actors.remove(actor) @@ -2118,23 +2540,59 @@ def _remove_actor_info(self, actor): exit_actors.remove(actor) break + self._actors_speed_perc.pop(actor, None) + if actor in self._all_actors: + self._all_actors.remove(actor) + def _destroy_actor(self, actor): """Destroy the actor and all its references""" self._remove_actor_info(actor) try: + actor.set_autopilot(False, self._tm_port) actor.destroy() except RuntimeError: pass - def _update_ego_route_location(self, location): - """Returns the closest route location to the ego""" - for index in range(self._route_index, min(self._route_index + self._route_buffer, self._route_length)): + def _update_ego_data(self): + """ + Checks the ego location to see if it has moved closer to the next route waypoint, + updating its information. This never checks for backwards movements to avoid unnedded confusion. + It also saves its max speed, used as a baseline for all BA vehicles. + """ + location = CarlaDataProvider.get_location(self._ego_actor) + prev_index = self._route_index + for index in range(self._route_index, min(self._route_index + self._route_buffer, self._route_length)): route_wp = self._route[index] + route_wp_dir = route_wp.transform.get_forward_vector() # Waypoint's forward vector veh_wp_dir = location - route_wp.transform.location # vector waypoint - vehicle dot_ve_wp = veh_wp_dir.x * route_wp_dir.x + veh_wp_dir.y * route_wp_dir.y + veh_wp_dir.z * route_wp_dir.z + if dot_ve_wp > 0: self._route_index = index - return self._route[self._route_index] + # Monitor route changes for those scenario that remove and readd a specific lane + if self._scenario_removed_lane: + for i in range(prev_index, self._route_index): + option_1 = self._route_options[i] + option_2 = self._route_options[i+1] + if option_1 == RoadOption.CHANGELANELEFT or option_2 == RoadOption.CHANGELANELEFT: + loc_1 = self._route[i].transform.location + loc_2 = self._route[i+1].transform.location + if abs(loc_1.distance(loc_2)) > 2.5: # Lane offset plus a bit forward + self._scenario_remove_lane_offset += 1 + elif option_1 == RoadOption.CHANGELANERIGHT or option_2 == RoadOption.CHANGELANERIGHT: + loc_1 = self._route[i].transform.location + loc_2 = self._route[i+1].transform.location + if abs(loc_1.distance(loc_2)) > 2.5: # Lane offset plus a bit forward + self._scenario_remove_lane_offset -= 1 + + self._ego_wp = self._route[self._route_index] + self._ego_key = get_lane_key(self._ego_wp) + self._ego_target_speed = self._ego_actor.get_speed_limit() + + if self.debug: + string = 'EGO_' + self._ego_state[0].upper() + debug_name = DEBUG_ROAD if self._ego_state == EGO_ROAD else DEBUG_JUNCTION + draw_string(self._world, location, string, debug_name, False) diff --git a/srunner/scenarios/background_activity_parametrizer.py b/srunner/scenarios/background_activity_parametrizer.py new file mode 100644 index 000000000..f11b8e7b7 --- /dev/null +++ b/srunner/scenarios/background_activity_parametrizer.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Module used to parse all the route and scenario configuration parameters. +""" + +from __future__ import print_function + +import py_trees + +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import (ChangeRoadBehavior, + ChangeOppositeBehavior, + ChangeJunctionBehavior) + +def get_parameter(config, name): + if name in config.other_parameters: + return float(config.other_parameters[name]['value']) + else: + return None + +class BackgroundActivityParametrizer(BasicScenario): + """ + This class holds everything required to change the parameters of the background activity. + Mainly used to change its behavior when, for example, moving from a highway into the city, + where we might want a different BA behavior. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + # Road + self._num_front_vehicles = get_parameter(config, "num_front_vehicles") + self._num_back_vehicles = get_parameter(config, "num_back_vehicles") + self._road_spawn_dist = get_parameter(config, "road_spawn_dist") + + # Opposite + self._opposite_source_dist = get_parameter(config, "opposite_source_dist") + self._opposite_max_actors = get_parameter(config, "opposite_max_actors") + self._opposite_spawn_dist = get_parameter(config, "opposite_spawn_dist") + self._opposite_active = get_parameter(config, "opposite_active") + + # Junction + self._junction_source_dist = get_parameter(config, "junction_source_dist") + self._junction_max_actors = get_parameter(config, "junction_max_actors") + self._junction_spawn_dist = get_parameter(config, "junction_spawn_dist") + self._junction_source_perc = get_parameter(config, "junction_source_perc") + + super().__init__("BackgroundActivityParametrizer", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _create_behavior(self): + """ + Hero vehicle is entering a junction in an urban area, at a signalized intersection, + while another actor runs a red lift, forcing the ego to break. + """ + + sequence = py_trees.composites.Sequence() + sequence.add_child(ChangeRoadBehavior(self._num_front_vehicles, self._num_back_vehicles, self._road_spawn_dist)) + sequence.add_child(ChangeJunctionBehavior( + self._junction_source_dist, self._junction_max_actors, self._junction_spawn_dist, self._junction_source_perc)) + sequence.add_child(ChangeOppositeBehavior( + self._opposite_source_dist, self._opposite_spawn_dist, self._opposite_active)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + return [] + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + diff --git a/srunner/scenarios/basic_scenario.py b/srunner/scenarios/basic_scenario.py index be646f2a4..39c3adf10 100644 --- a/srunner/scenarios/basic_scenario.py +++ b/srunner/scenarios/basic_scenario.py @@ -16,11 +16,13 @@ import carla -import srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions as conditions +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (WaitForBlackboardVariable, + InTimeToArrivalToLocation) +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import WaitForever from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.timer import TimeOut -from srunner.scenariomanager.weather_sim import WeatherBehavior from srunner.scenariomanager.scenarioatomics.atomic_behaviors import UpdateAllActorControls +from srunner.scenariomanager.scenarioatomics.atomic_criteria import Criterion class BasicScenario(object): @@ -35,53 +37,104 @@ def __init__(self, name, ego_vehicles, config, world, Setup all relevant parameters and create scenario and instantiate scenario manager """ - self.other_actors = [] - if not self.timeout: # pylint: disable=access-member-before-definition - self.timeout = 60 # If no timeout was provided, set it to 60 seconds - - self.criteria_list = [] # List of evaluation criteria - self.scenario = None - self.world = world - - self.ego_vehicles = ego_vehicles self.name = name + self.ego_vehicles = ego_vehicles + self.other_actors = [] + self.parking_slots = [] self.config = config + self.world = world + self.debug_mode = debug_mode self.terminate_on_failure = terminate_on_failure + self.criteria_enable = criteria_enable + + self.route_mode = bool(config.route) + self.behavior_tree = None + self.criteria_tree = None + + # If no timeout was provided, set it to 60 seconds + if not hasattr(self, 'timeout'): + self.timeout = 60 + if debug_mode: + py_trees.logging.level = py_trees.logging.Level.DEBUG - self._initialize_environment(world) + if not self.route_mode: + # Only init env for route mode, avoid duplicate initialization during runtime + self._initialize_environment(world) - # Initializing adversarial actors self._initialize_actors(config) - if CarlaDataProvider.is_sync_mode(): + + if CarlaDataProvider.is_runtime_init_mode(): + world.wait_for_tick() + elif CarlaDataProvider.is_sync_mode(): world.tick() else: world.wait_for_tick() - # Setup scenario - if debug_mode: - py_trees.logging.level = py_trees.logging.Level.DEBUG - - behavior = self._create_behavior() + # Main scenario tree + self.scenario_tree = py_trees.composites.Parallel(name, policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) - criteria = None - if criteria_enable: - criteria = self._create_test_criteria() + # Add a trigger and end condition to the behavior to ensure it is only activated when it is relevant + self.behavior_tree = py_trees.composites.Sequence() - # Add a trigger condition for the behavior to ensure the behavior is only activated, when it is relevant - behavior_seq = py_trees.composites.Sequence() trigger_behavior = self._setup_scenario_trigger(config) if trigger_behavior: - behavior_seq.add_child(trigger_behavior) + self.behavior_tree.add_child(trigger_behavior) - if behavior is not None: - behavior_seq.add_child(behavior) - behavior_seq.name = 'VehicleBehavior' + scenario_behavior = self._create_behavior() + if scenario_behavior is not None: + self.behavior_tree.add_child(scenario_behavior) + self.behavior_tree.name = scenario_behavior.name end_behavior = self._setup_scenario_end(config) if end_behavior: - behavior_seq.add_child(end_behavior) + self.behavior_tree.add_child(end_behavior) + + # Create the lights behavior + lights = self._create_lights_behavior() + if lights: + self.scenario_tree.add_child(lights) + + # Create the weather behavior + weather = self._create_weather_behavior() + if weather: + self.scenario_tree.add_child(weather) + + # And then add it to the main tree + self.scenario_tree.add_child(self.behavior_tree) + + # Create the criteria tree (if needed) + if self.criteria_enable: + criteria = self._create_test_criteria() - self.scenario = Scenario(behavior_seq, criteria, self.name, self.timeout, self.terminate_on_failure) + # All the work is done, thanks! + if isinstance(criteria, py_trees.composites.Composite): + self.criteria_tree = criteria + + # Lazy mode, but its okay, we'll create the parallel behavior tree for you. + elif isinstance(criteria, list): + for criterion in criteria: + criterion.terminate_on_failure = terminate_on_failure + + self.criteria_tree = py_trees.composites.Parallel(name="Test Criteria", + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL) + self.criteria_tree.add_children(criteria) + self.criteria_tree.setup(timeout=1) + + else: + raise ValueError("WARNING: Scenario {} couldn't be setup, make sure the criteria is either " + "a list or a py_trees.composites.Composite".format(self.name)) + + self.scenario_tree.add_child(self.criteria_tree) + + # Create the timeout behavior + self.timeout_node = self._create_timeout_behavior() + if self.timeout_node: + self.scenario_tree.add_child(self.timeout_node) + + # Add other nodes + self.scenario_tree.add_child(UpdateAllActorControls()) + + self.scenario_tree.setup(timeout=1) def _initialize_environment(self, world): """ @@ -126,48 +179,35 @@ def _setup_scenario_trigger(self, config): The function can be overloaded by a user implementation inside the user-defined scenario class. """ - start_location = None if config.trigger_points and config.trigger_points[0]: - start_location = config.trigger_points[0].location # start location of the scenario - - ego_vehicle_route = CarlaDataProvider.get_ego_vehicle_route() - - if start_location: - if ego_vehicle_route: - if config.route_var_name is None: # pylint: disable=no-else-return - return conditions.InTriggerDistanceToLocationAlongRoute(self.ego_vehicles[0], - ego_vehicle_route, - start_location, - 5) - else: - check_name = "WaitForBlackboardVariable: {}".format(config.route_var_name) - return conditions.WaitForBlackboardVariable(name=check_name, - variable_name=config.route_var_name, - variable_value=True, - var_init_value=False) - - return conditions.InTimeToArrivalToLocation(self.ego_vehicles[0], - 2.0, - start_location) - - return None + start_location = config.trigger_points[0].location + else: + return None + + # Scenario is not part of a route, wait for the ego to move + if not self.route_mode or config.route_var_name is None: + return InTimeToArrivalToLocation(self.ego_vehicles[0], 2.0, start_location) + + # Scenario is part of a route. + check_name = "WaitForBlackboardVariable: {}".format(config.route_var_name) + return WaitForBlackboardVariable(config.route_var_name, True, False, name=check_name) def _setup_scenario_end(self, config): """ This function adds and additional behavior to the scenario, which is triggered - after it has ended. - + after it has ended. The Blackboard variable is set to False to indicate the scenario has ended. The function can be overloaded by a user implementation inside the user-defined scenario class. """ - ego_vehicle_route = CarlaDataProvider.get_ego_vehicle_route() + if not self.route_mode or config.route_var_name is None: + return None + + # Scenario is part of a route. + end_sequence = py_trees.composites.Sequence() + name = "Reset Blackboard Variable: {} ".format(config.route_var_name) + end_sequence.add_child(py_trees.blackboard.SetBlackboardVariable(name, config.route_var_name, False)) + end_sequence.add_child(WaitForever()) # scenario can't stop the route - if ego_vehicle_route: - if config.route_var_name is not None: - set_name = "Reset Blackboard Variable: {} ".format(config.route_var_name) - return py_trees.blackboard.SetBlackboardVariable(name=set_name, - variable_name=config.route_var_name, - variable_value=False) - return None + return end_sequence def _create_behavior(self): """ @@ -186,6 +226,29 @@ def _create_test_criteria(self): "This function is re-implemented by all scenarios" "If this error becomes visible the class hierarchy is somehow broken") + def _create_weather_behavior(self): + """ + Default empty initialization of the weather behavior, + responsible of controlling the weather during the simulation. + Override this method in child class to provide custom initialization. + """ + pass + + def _create_lights_behavior(self): + """ + Default empty initialization of the lights behavior, + responsible of controlling the street lights during the simulation. + Override this method in child class to provide custom initialization. + """ + pass + + def _create_timeout_behavior(self): + """ + Default initialization of the timeout behavior. + Override this method in child class to provide custom initialization. + """ + return TimeOut(self.timeout, name="TimeOut") # Timeout node + def change_control(self, control): # pylint: disable=no-self-use """ This is a function that changes the control based on the scenario determination @@ -196,68 +259,21 @@ def change_control(self, control): # pylint: disable=no-self-use """ return control - def remove_all_actors(self): + def get_criteria(self): """ - Remove all actors + Return the list of test criteria, including all the leaf nodes. + Some criteria might have trigger conditions, which have to be filtered out. """ - for i, _ in enumerate(self.other_actors): - if self.other_actors[i] is not None: - if CarlaDataProvider.actor_id_exists(self.other_actors[i].id): - CarlaDataProvider.remove_actor_by_id(self.other_actors[i].id) - self.other_actors[i] = None - self.other_actors = [] - - -class Scenario(object): - - """ - Basic scenario class. This class holds the behavior_tree describing the - scenario and the test criteria. - - The user must not modify this class. + criteria = [] + if not self.criteria_tree: + return criteria - Important parameters: - - behavior: User defined scenario with py_tree - - criteria_list: List of user defined test criteria with py_tree - - timeout (default = 60s): Timeout of the scenario in seconds - - terminate_on_failure: Terminate scenario on first failure - """ - - def __init__(self, behavior, criteria, name, timeout=60, terminate_on_failure=False): - self.behavior = behavior - self.test_criteria = criteria - self.timeout = timeout - self.name = name + criteria_nodes = self._extract_nodes_from_tree(self.criteria_tree) + for criterion in criteria_nodes: + if isinstance(criterion, Criterion): + criteria.append(criterion) - if self.test_criteria is not None and not isinstance(self.test_criteria, py_trees.composites.Parallel): - # list of nodes - for criterion in self.test_criteria: - criterion.terminate_on_failure = terminate_on_failure - - # Create py_tree for test criteria - self.criteria_tree = py_trees.composites.Parallel( - name="Test Criteria", - policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE - ) - self.criteria_tree.add_children(self.test_criteria) - self.criteria_tree.setup(timeout=1) - else: - self.criteria_tree = criteria - - # Create node for timeout - self.timeout_node = TimeOut(self.timeout, name="TimeOut") - - # Create overall py_tree - self.scenario_tree = py_trees.composites.Parallel(name, policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) - if behavior is not None: - self.scenario_tree.add_child(self.behavior) - self.scenario_tree.add_child(self.timeout_node) - self.scenario_tree.add_child(WeatherBehavior()) - self.scenario_tree.add_child(UpdateAllActorControls()) - - if criteria is not None: - self.scenario_tree.add_child(self.criteria_tree) - self.scenario_tree.setup(timeout=1) + return criteria def _extract_nodes_from_tree(self, tree): # pylint: disable=no-self-use """ @@ -279,13 +295,6 @@ def _extract_nodes_from_tree(self, tree): # pylint: disable=no-self-use return node_list - def get_criteria(self): - """ - Return the list of test criteria (all leave nodes) - """ - criteria_list = self._extract_nodes_from_tree(self.criteria_tree) - return criteria_list - def terminate(self): """ This function sets the status of all leaves in the scenario tree to INVALID @@ -307,3 +316,22 @@ def terminate(self): for actor_id in actor_dict: actor_dict[actor_id].reset() py_trees.blackboard.Blackboard().set("ActorsWithController", {}, overwrite=True) + + def remove_all_actors(self): + """ + Remove all actors + """ + if not hasattr(self, 'other_actors'): + return + for i, _ in enumerate(self.other_actors): + if self.other_actors[i] is not None: + if CarlaDataProvider.actor_id_exists(self.other_actors[i].id): + CarlaDataProvider.remove_actor_by_id(self.other_actors[i].id) + self.other_actors[i] = None + self.other_actors = [] + + def get_parking_slots(self): + """ + Returns occupied parking slots. + """ + return self.parking_slots diff --git a/srunner/scenarios/blocked_intersection.py b/srunner/scenarios/blocked_intersection.py new file mode 100644 index 000000000..68b8f4f3e --- /dev/null +++ b/srunner/scenarios/blocked_intersection.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenario with low visibility, the ego performs a turn only to find out that the end is blocked by another vehicle. +""" + +from __future__ import print_function + +import carla +import py_trees +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider + +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + Idle, + ScenarioTimeout, + ActorTransformSetter, + HandBrakeVehicle) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import InTriggerDistanceToVehicle + +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import HandleJunctionScenario + +from srunner.tools.scenario_helper import generate_target_waypoint_in_route + + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + + +class BlockedIntersection(BasicScenario): + """ + This class holds everything required for a scenario in which, + the ego performs a turn only to find out that the end is blocked by another vehicle. + """ + + def __init__(self, world, ego_vehicles, config, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._map.get_waypoint(self._trigger_location) + + self._blocker_distance = 5 + self._trigger_distance = 13 + self._stop_time = 10 + + self._scenario_timeout = 240 + + self._blocker_transform = None + + super().__init__("BlockedIntersection", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + waypoint = generate_target_waypoint_in_route(self._reference_waypoint, config.route) + waypoint = waypoint.next(self._blocker_distance)[0] + + self._blocker_transform = waypoint.transform + + # Spawn the blocker vehicle + blocker = CarlaDataProvider.request_new_actor( + "vehicle.*.*", self._blocker_transform, + attribute_filter={'base_type': 'car', 'has_lights': True, 'special_type': ''} + ) + if blocker is None: + raise Exception("Couldn't spawn the blocker vehicle") + self.other_actors.append(blocker) + + blocker.set_simulate_physics(False) + blocker.set_location(self._blocker_transform.location + carla.Location(z=-200)) + + lights = blocker.get_light_state() + lights |= carla.VehicleLightState.Brake + blocker.set_light_state(carla.VehicleLightState(lights)) + + def _create_behavior(self): + """ + Just wait for a while after the ego closes in on the blocker, then remove it. + """ + sequence = py_trees.composites.Sequence(name="BlockedIntersection") + + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=[], + remove_exits=[], + stop_entries=True, + extend_road_exit=0 + )) + # Ego go behind the blocker + main_behavior = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + main_behavior.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + behavior = py_trees.composites.Sequence(name="Approach and Wait") + behavior.add_child(ActorTransformSetter(self.other_actors[0], self._blocker_transform, True)) + behavior.add_child(HandBrakeVehicle(self.other_actors[0], 1)) + behavior.add_child(InTriggerDistanceToVehicle( + self.other_actors[-1], self.ego_vehicles[0], self._trigger_distance)) + behavior.add_child(Idle(self._stop_time)) + main_behavior.add_child(behavior) + + sequence.add_child(main_behavior) + sequence.add_child(ActorDestroy(self.other_actors[0])) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/construction_crash_vehicle.py b/srunner/scenarios/construction_crash_vehicle.py index d6c2f228d..da1cc7769 100644 --- a/srunner/scenarios/construction_crash_vehicle.py +++ b/srunner/scenarios/construction_crash_vehicle.py @@ -14,66 +14,126 @@ import carla from srunner.scenariomanager.carla_data_provider import CarlaDataProvider -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ActorDestroy -from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import DriveDistance -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import Idle -from srunner.tools.scenario_helper import get_location_in_distance_from_wp -from srunner.scenarios.object_crash_vehicle import StationaryObjectCrossing +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + ActorTransformSetter, + SwitchWrongDirectionTest, + ScenarioTimeout, + Idle, WaitForever, + OppositeActorFlow) +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, + WaitUntilInFrontPosition) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import (RemoveRoadLane, + ReAddRoadLane, + SetMaxSpeed, + ChangeOppositeBehavior) -class ConstructionSetupCrossing(StationaryObjectCrossing): +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default + +class ConstructionObstacle(BasicScenario): """ This class holds everything required for a construction scenario The ego vehicle is passing through a road and encounters - a stationary rectangular construction cones setup and traffic warning. + a stationary rectangular construction cones setup and traffic warning, + forcing it to lane change. This is a single ego vehicle scenario """ - def __init__( - self, - world, - ego_vehicles, - config, - randomize=False, - debug_mode=False, - criteria_enable=True, - timeout=60): + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, + criteria_enable=True, timeout=60): """ Setup all relevant parameters and create scenario + and instantiate scenario manager """ - super( - ConstructionSetupCrossing, - self).__init__( - world, - ego_vehicles=ego_vehicles, - config=config, - randomize=randomize, - debug_mode=debug_mode, - criteria_enable=criteria_enable) + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._trigger_distance = 30 + self._opposite_wait_duration = 5 + self._end_distance = 50 + + self._reference_waypoint = self._map.get_waypoint(config.trigger_points[0].location) + self._construction_wp = None + + self._construction_transforms = [] + + self._distance = get_value_parameter(config, 'distance', float, 100) + self._max_speed = get_value_parameter(config, 'speed', float, 60) + self._scenario_timeout = 240 + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + super().__init__("ConstructionObstacle", ego_vehicles, config, world, debug_mode, False, criteria_enable) def _initialize_actors(self, config): - """ - Custom initialization - """ - _start_distance = 40 - lane_width = self._reference_waypoint.lane_width - location, _ = get_location_in_distance_from_wp( - self._reference_waypoint, _start_distance) - waypoint = self._wmap.get_waypoint(location) - self._create_construction_setup(waypoint.transform, lane_width) - - def create_cones_side( - self, - start_transform, - forward_vector, - z_inc=0, - cone_length=0, - cone_offset=0): - """ - Creates One Side of the Cones - """ + """Creates all props part of the construction""" + self._spawn_side_prop(self._reference_waypoint) + + wps = self._reference_waypoint.next(self._distance) + if not wps: + raise ValueError("Couldn't find a viable position to set up the construction actors") + self._construction_wp = wps[0] + self._create_construction_setup(self._construction_wp.transform, self._reference_waypoint.lane_width) + + self._end_wp = self._move_waypoint_forward(self._construction_wp, self._end_distance) + + def _move_waypoint_forward(self, wp, distance): + dist = 0 + next_wp = wp + while dist < distance: + next_wps = next_wp.next(1) + if not next_wps or next_wps[0].is_junction: + break + next_wp = next_wps[0] + dist += 1 + return next_wp + + def _spawn_side_prop(self, wp): + """Spawn the accident indication signal""" + prop_wp = wp + while True: + if self._direction == "right": + wp = prop_wp.get_right_lane() + else: + wp = prop_wp.get_left_lane() + if wp is None or wp.lane_type not in (carla.LaneType.Driving, carla.LaneType.Parking): + break + prop_wp = wp + + displacement = 0.3 * prop_wp.lane_width + r_vec = prop_wp.transform.get_right_vector() + if self._direction == 'left': + r_vec *= -1 + + spawn_transform = wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=0.2) + spawn_transform.rotation.yaw += 90 + signal_prop = CarlaDataProvider.request_new_actor('static.prop.warningconstruction', spawn_transform) + if not signal_prop: + raise ValueError("Couldn't spawn the indication prop asset") + signal_prop.set_simulate_physics(False) + self.other_actors.append(signal_prop) + + def _create_cones_side(self, start_transform, forward_vector, z_inc=0, cone_length=0, cone_offset=0): + """Creates the cones at the side""" _dist = 0 while _dist < (cone_length * cone_offset): # Move forward @@ -82,29 +142,30 @@ def create_cones_side( location = start_transform.location + forward_dist location.z += z_inc - transform = carla.Transform(location, start_transform.rotation) + spawn_transform = carla.Transform(location, start_transform.rotation) + spawn_transform.location.z -= 200 + cone_transform = carla.Transform(location, start_transform.rotation) - cone = CarlaDataProvider.request_new_actor( - 'static.prop.constructioncone', transform) - cone.set_simulate_physics(True) + cone = CarlaDataProvider.request_new_actor('static.prop.constructioncone', spawn_transform) + cone.set_simulate_physics(False) self.other_actors.append(cone) + self._construction_transforms.append([cone, cone_transform]) + def _create_construction_setup(self, start_transform, lane_width): - """ - Create Construction Setup - """ + """Create construction setup""" - _initial_offset = {'cones': {'yaw': 180, 'k': lane_width / 2.0}, + _initial_offset = {'cones': {'yaw': 270, 'k': 0.85 * lane_width / 2.0}, 'warning_sign': {'yaw': 180, 'k': 5, 'z': 0}, 'debris': {'yaw': 0, 'k': 2, 'z': 1}} _prop_names = {'warning_sign': 'static.prop.trafficwarning', 'debris': 'static.prop.dirtdebris02'} _perp_angle = 90 - _setup = {'lengths': [0, 6, 3], 'offsets': [0, 2, 1]} + _setup = {'lengths': [4, 3], 'offsets': [2, 1]} _z_increment = 0.1 - ############################# Traffic Warning and Debris ############## + # Traffic warning and debris for key, value in _initial_offset.items(): if key == 'cones': continue @@ -116,22 +177,31 @@ def _create_construction_setup(self, start_transform, lane_width): transform.rotation.get_forward_vector() transform.location.z += value['z'] transform.rotation.yaw += _perp_angle + + spawn_transform = carla.Transform(transform.location, transform.rotation) + spawn_transform.location.z -= 200 static = CarlaDataProvider.request_new_actor( - _prop_names[key], transform) - static.set_simulate_physics(True) + _prop_names[key], spawn_transform) + static.set_simulate_physics(False) self.other_actors.append(static) - ############################# Cones ################################### + self._construction_transforms.append([static, transform]) + + # Cones side_transform = carla.Transform( start_transform.location, start_transform.rotation) side_transform.rotation.yaw += _perp_angle - side_transform.location -= _initial_offset['cones']['k'] * \ - side_transform.rotation.get_forward_vector() + offset_vec = _initial_offset['cones']['k'] * side_transform.rotation.get_forward_vector() + if self._direction == 'right': + side_transform.location -= offset_vec + else: + side_transform.location += offset_vec + side_transform.rotation.yaw += _initial_offset['cones']['yaw'] for i in range(len(_setup['lengths'])): - self.create_cones_side( + self._create_cones_side( side_transform, forward_vector=side_transform.rotation.get_forward_vector(), z_inc=_z_increment, @@ -139,27 +209,111 @@ def _create_construction_setup(self, start_transform, lane_width): cone_offset=_setup['offsets'][i]) side_transform.location += side_transform.get_forward_vector() * \ _setup['lengths'][i] * _setup['offsets'][i] - side_transform.rotation.yaw += _perp_angle + if i == 0 and self._direction == 'left': + side_transform.rotation.yaw -= _perp_angle + else: + side_transform.rotation.yaw += _perp_angle + + def _create_behavior(self): + """ + Remove the lane that would collide with the construction and add the construction props. + Wait until the ego is close to the construction (and a bit more) before changing the side traffic + Readd the traffic at the end + """ + root = py_trees.composites.Sequence(name="ConstructionObstacle") + if self.route_mode: + root.add_child(RemoveRoadLane(self._reference_waypoint)) + + for actor, transform in self._construction_transforms: + root.add_child(ActorTransformSetter(actor, transform, True)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) + + behavior = py_trees.composites.Sequence() + behavior.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._construction_wp.transform.location, self._trigger_distance)) + behavior.add_child(Idle(self._opposite_wait_duration)) + if self.route_mode: + behavior.add_child(SetMaxSpeed(self._max_speed)) + behavior.add_child(WaitForever()) + + end_condition.add_child(behavior) + root.add_child(end_condition) + + if self.route_mode: + root.add_child(SetMaxSpeed(0)) + root.add_child(ReAddRoadLane(0)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class ConstructionObstacleTwoWays(ConstructionObstacle): + """ + Variation of ConstructionObstacle where the ego has to invade the opposite lane + """ + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=60): + + self._opposite_interval = get_interval_parameter(config, 'frequency', float, [20, 100]) + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) def _create_behavior(self): """ - Only behavior here is to wait + Remove the lane that would collide with the construction and add the construction props. + Wait until the ego is close to the construction (and a bit more) before changing the opposite traffic + Readd the traffic at the end, and allow the ego to invade the opposite lane by deactivating the criteria """ - # leaf nodes - actor_stand = Idle(15) + reference_wp = self._construction_wp.get_left_lane() + if not reference_wp: + raise ValueError("Couldnt find a left lane to spawn the opposite traffic") + + root = py_trees.composites.Sequence(name="ConstructionObstacleTwoWays") + if self.route_mode: + root.add_child(RemoveRoadLane(self._reference_waypoint)) - end_condition = DriveDistance( - self.ego_vehicles[0], - self._ego_vehicle_distance_driven) + for actor, transform in self._construction_transforms: + root.add_child(ActorTransformSetter(actor, transform, True)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) - # non leaf nodes - scenario_sequence = py_trees.composites.Sequence() + behavior = py_trees.composites.Sequence() + behavior.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._construction_wp.transform.location, self._trigger_distance)) + behavior.add_child(Idle(self._opposite_wait_duration)) + if self.route_mode: + behavior.add_child(SwitchWrongDirectionTest(False)) + behavior.add_child(ChangeOppositeBehavior(active=False)) + behavior.add_child(OppositeActorFlow(reference_wp, self.ego_vehicles[0], self._opposite_interval)) - # building tree - scenario_sequence.add_child(actor_stand) + end_condition.add_child(behavior) + root.add_child(end_condition) - for i, _ in enumerate(self.other_actors): - scenario_sequence.add_child(ActorDestroy(self.other_actors[i])) - scenario_sequence.add_child(end_condition) + if self.route_mode: + root.add_child(SwitchWrongDirectionTest(True)) + root.add_child(ChangeOppositeBehavior(active=True)) + root.add_child(ReAddRoadLane(0)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) - return scenario_sequence + return root diff --git a/srunner/scenarios/control_loss.py b/srunner/scenarios/control_loss.py index f7d302658..4f5eaa65b 100644 --- a/srunner/scenarios/control_loss.py +++ b/srunner/scenarios/control_loss.py @@ -14,11 +14,14 @@ from numpy import random import py_trees +import operator + import carla from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import AddNoiseToRouteEgo from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest -from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import DriveDistance +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import DriveDistance, InTriggerDistanceToLocation from srunner.scenarios.basic_scenario import BasicScenario from srunner.tools.scenario_helper import get_waypoint_in_distance @@ -38,46 +41,50 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals """ self.timeout = timeout self._randomize = randomize + self._rng = CarlaDataProvider.get_random_seed() self._map = CarlaDataProvider.get_map() self._end_distance = 110 - super(ControlLoss, self).__init__("ControlLoss", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) + # Friction loss tends to have a much stronger steering compoenent then a throttle one + self._throttle_mean = 0.03 + self._throttle_std = 0.01 + self._steer_mean = 0.055 + self._steer_std = 0.015 + + self._trigger_dist = 2 + + super().__init__("ControlLoss", ego_vehicles, config, world, debug_mode, criteria_enable=criteria_enable) def _initialize_actors(self, config): """ Custom initialization """ if self._randomize: - self._distance = random.randint(low=10, high=80, size=3) + self._distance = list(self._rng.randint(low=10, high=80, size=3)) self._distance = sorted(self._distance) + self._offset = list(2 * random.rand(3) - 1) else: self._distance = [14, 48, 74] + self._offset = [-0.6, 0.8, 0.2] self._reference_waypoint = self._map.get_waypoint(config.trigger_points[0].location) # Get the debris locations first_wp, _ = get_waypoint_in_distance(self._reference_waypoint, self._distance[0]) - first_ground_loc = self.world.ground_projection(first_wp.transform.location, 2) - self.first_loc_prev = first_ground_loc.location if first_ground_loc else first_wp.transform.location + first_ground_loc = self.world.ground_projection(first_wp.transform.location + carla.Location(z=1), 2) + first_loc = first_ground_loc.location if first_ground_loc else first_wp.transform.location + self.first_transform = carla.Transform(first_loc, first_wp.transform.rotation) second_wp, _ = get_waypoint_in_distance(self._reference_waypoint, self._distance[1]) - second_ground_loc = self.world.ground_projection(second_wp.transform.location, 2) - self.second_loc_prev = second_ground_loc.location if second_ground_loc else second_wp.transform.location + second_ground_loc = self.world.ground_projection(second_wp.transform.location + carla.Location(z=1), 2) + second_loc = second_ground_loc.location if second_ground_loc else second_wp.transform.location + self.second_transform = carla.Transform(second_loc, second_wp.transform.rotation) third_wp, _ = get_waypoint_in_distance(self._reference_waypoint, self._distance[2]) - third_ground_loc = self.world.ground_projection(third_wp.transform.location, 2) - self.third_loc_prev = third_ground_loc.location if third_ground_loc else third_wp.transform.location - - # Get the debris transforms - self.first_transform = carla.Transform(self.first_loc_prev, first_wp.transform.rotation) - self.second_transform = carla.Transform(self.second_loc_prev, second_wp.transform.rotation) - self.third_transform = carla.Transform(self.third_loc_prev, third_wp.transform.rotation) + third_ground_loc = self.world.ground_projection(third_wp.transform.location + carla.Location(z=1), 2) + third_loc = third_ground_loc.location if third_ground_loc else third_wp.transform.location + self.third_transform = carla.Transform(third_loc, third_wp.transform.rotation) # Spawn the debris first_debris = CarlaDataProvider.request_new_actor( @@ -87,10 +94,6 @@ def _initialize_actors(self, config): third_debris = CarlaDataProvider.request_new_actor( 'static.prop.dirtdebris01', self.third_transform, rolename='prop') - first_debris.set_transform(self.first_transform) - second_debris.set_transform(self.second_transform) - third_debris.set_transform(self.third_transform) - # Remove their physics first_debris.set_simulate_physics(False) second_debris.set_simulate_physics(False) @@ -100,22 +103,73 @@ def _initialize_actors(self, config): self.other_actors.append(second_debris) self.other_actors.append(third_debris) + def _get_noise_parameters(self): + """Randomizes the mean to be either positive or negative""" + return [ + self._rng.choice([self._throttle_mean, -self._throttle_mean]), + self._throttle_std, + self._rng.choice([self._steer_mean, -self._steer_mean]), + self._steer_std + ] + def _create_behavior(self): """ The scenario defined after is a "control loss vehicle" scenario. """ - sequence = py_trees.composites.Sequence("ControlLoss") - sequence.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) - return sequence + root = py_trees.composites.Parallel("ControlLoss", py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + sequence = py_trees.composites.Sequence() + + # First debris behavior + sequence.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self.first_transform.location, self._trigger_dist)) + + noise_1 = self._get_noise_parameters() + noise_behavior_1 = py_trees.composites.Parallel("Add Noise 1", py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + if self.route_mode: + noise_behavior_1.add_child(AddNoiseToRouteEgo(self.ego_vehicles[0], *noise_1)) + noise_behavior_1.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self.first_transform.location, self._trigger_dist, operator.gt)) + sequence.add_child(noise_behavior_1) + + # Second debris behavior + sequence.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self.second_transform.location, self._trigger_dist)) + + noise_2 = self._get_noise_parameters() + noise_behavior_2 = py_trees.composites.Parallel("Add Noise 2", py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + if self.route_mode: + noise_behavior_2.add_child(AddNoiseToRouteEgo(self.ego_vehicles[0], *noise_2)) + noise_behavior_2.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self.second_transform.location, self._trigger_dist, operator.gt)) + sequence.add_child(noise_behavior_2) + + # Third debris behavior + sequence.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self.third_transform.location, self._trigger_dist)) + + noise_3 = self._get_noise_parameters() + noise_behavior_3 = py_trees.composites.Parallel("Add Noise 3", py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + if self.route_mode: + noise_behavior_3.add_child(AddNoiseToRouteEgo(self.ego_vehicles[0], *noise_3)) + noise_behavior_3.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self.third_transform.location, self._trigger_dist, operator.gt)) + sequence.add_child(noise_behavior_3) + + end_distance = self._end_distance - self._distance[-1] + sequence.add_child(DriveDistance(self.ego_vehicles[0], end_distance)) + + root.add_child(sequence) + root.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + return root def _create_test_criteria(self): """ A list of all test criteria will be created that is later used in parallel behavior tree. """ - criteria = [] - criteria.append(CollisionTest(self.ego_vehicles[0])) - return criteria + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] def __del__(self): """ diff --git a/srunner/scenarios/cross_bicycle_flow.py b/srunner/scenarios/cross_bicycle_flow.py new file mode 100644 index 000000000..fd13bf310 --- /dev/null +++ b/srunner/scenarios/cross_bicycle_flow.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2022 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenarios in which the ego has to cross a flow of bycicles +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import BicycleFlow, TrafficLightFreezer, ScenarioTimeout +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitEndIntersection +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import HandleJunctionScenario +from agents.navigation.local_planner import RoadOption + + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + + +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default + +class CrossingBicycleFlow(BasicScenario): + """ + This class holds everything required for a scenario in which another vehicle runs a red light + in front of the ego, forcing it to react. This vehicles are 'special' ones such as police cars, + ambulances or firetrucks. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._start_flow = convert_dict_to_location(config.other_parameters['start_actor_flow']) + self._end_dist_flow = 40 # m + self._sink_distance = 2 + + self._end_distance = 40 + + self._signalized_junction = False + + self._reference_waypoint = self._map.get_waypoint(config.trigger_points[0].location) + + self._green_light_delay = 5 + self._scenario_timeout = 240 + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 10) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [20, 50]) + + super().__init__("CrossingBicycleFlow", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + + ego_location = config.trigger_points[0].location + self._ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) + + # Get the junction + starting_wp = self._ego_wp + ego_junction_dist = 0 + while not starting_wp.is_junction: + starting_wps = starting_wp.next(1.0) + if len(starting_wps) == 0: + raise ValueError("Failed to find junction as a waypoint with no next was detected") + starting_wp = starting_wps[0] + ego_junction_dist += 1 + junction = starting_wp.get_junction() + + # Get the plan + self._source_wp = self._map.get_waypoint(self._start_flow, lane_type=carla.LaneType.Biking) + if not self._source_wp or self._source_wp.transform.location.distance(self._start_flow) > 10: + raise ValueError("Couldn't find a biking lane at the specified location") + + self._plan = [] + plan_step = 0 + wp = self._source_wp + while True: + next_wps = wp.next(2) + if not next_wps: + raise ValueError("Couldn't find a proper plan for the bicycle flow") + next_wp = next_wps + wp = next_wp[0] + self._plan.append([next_wp[0], RoadOption.LANEFOLLOW]) + + if plan_step == 0 and wp.is_junction: + plan_step += 1 + elif plan_step == 1 and not wp.is_junction: + plan_step += 1 + exit_loc = wp.transform.location + elif plan_step == 2 and exit_loc.distance(wp.transform.location) > self._end_dist_flow: + break + + tls = self._world.get_traffic_lights_in_junction(junction.id) + if not tls: + self._signalized_junction = False + else: + self._signalized_junction = True + self._get_traffic_lights(tls, ego_junction_dist) + + def _get_traffic_lights(self, tls, ego_dist): + """Get the traffic light of the junction, mapping their states""" + + ego_landmark = self._ego_wp.get_landmarks_of_type(ego_dist + 2, "1000001")[0] + ego_tl = self._world.get_traffic_light(ego_landmark) + self._flow_tl_dict = {} + self._init_tl_dict = {} + for tl in tls: + if tl.id == ego_tl.id: + self._flow_tl_dict[tl] = carla.TrafficLightState.Green + self._init_tl_dict[tl] = carla.TrafficLightState.Red + else: + self._flow_tl_dict[tl] = carla.TrafficLightState.Red + self._init_tl_dict[tl] = carla.TrafficLightState.Red + + + def _create_behavior(self): + """ + Hero vehicle is entering a junction in an urban area, at a signalized intersection, + while another actor runs a red lift, forcing the ego to break. + """ + + root = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(BicycleFlow(self._plan, self._source_dist_interval, self._sink_distance, self._flow_speed, True)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + root.add_child(WaitEndIntersection(self.ego_vehicles[0])) + + # Freeze the traffic lights to allow the flow to populate the junction + if self._signalized_junction: + tl_freezer_sequence = py_trees.composites.Sequence("Traffic Light Behavior") + tl_freezer_sequence.add_child(TrafficLightFreezer(self._init_tl_dict, duration=self._green_light_delay)) + tl_freezer_sequence.add_child(TrafficLightFreezer(self._flow_tl_dict)) + root.add_child(tl_freezer_sequence) + + # Add the BackgroundActivity behaviors + if not self.route_mode: + return root + + sequence = py_trees.composites.Sequence() + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=[], + remove_exits=[], + stop_entries=True, + extend_road_exit=0 + )) + sequence.add_child(root) + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/cut_in_with_static_vehicle.py b/srunner/scenarios/cut_in_with_static_vehicle.py new file mode 100644 index 000000000..01d345bfd --- /dev/null +++ b/srunner/scenarios/cut_in_with_static_vehicle.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python + +# Copyright (c) 2019 Intel Corporation +# Copyright (c) 2019-2022 Intel Corporation + +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +from __future__ import print_function + +import py_trees +import carla + +from agents.navigation.local_planner import RoadOption + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + BatchActorTransformSetter, + CutIn, + BasicAgentBehavior, + Idle) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, + InTimeToArrivalToLocation) +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import RemoveRoadLane, LeaveSpaceInFront, ReAddRoadLane, ChangeRoadBehavior + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + + +class StaticCutIn(BasicScenario): + + """ + Cut in(with static vehicle) scenario synchronizes a vehicle that is parked at a side lane + to cut in in front of the ego vehicle, forcing it to break + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=60): + """ + Setup all relevant parameters and create scenario + """ + self._wmap = CarlaDataProvider.get_map() + self.timeout = timeout + + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) + + self._reaction_time = 2.7 # Time the agent has to react to avoid the collision [s] + self._min_trigger_dist = 15.0 # Min distance to the collision location that triggers the adversary [m] + + self._back_vehicles = 2 + self._front_vehicles = 3 + self._vehicle_gap = 11 + + self._speed = 60 # Km/h + + self._adversary_end_distance = 70 + + self._extra_space = 30 # Leave extra space as a vehicle is invading the ego's lane (BA parameter) + + self._side_transforms = [] + self._side_wp = None + + self._attributes = {'base_type': 'car', 'has_lights': True} + + self._blocker_distance = get_value_parameter(config, 'distance', float, 100) + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + super().__init__("StaticCutIn", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + # Spawn the blocker vehicle + next_wps = self._reference_waypoint.next(self._blocker_distance) + if not next_wps: + raise ValueError("Couldn't find a proper position for the cut in vehicle") + blocker_wp = next_wps[0] + + # Spawn the vehicles behind the cut in one + for i in range(self._back_vehicles): + # Move to the side + side_wp = blocker_wp.get_left_lane() if self._direction == 'left' else blocker_wp.get_right_lane() + if not side_wp: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + + if i == 1: + self._side_wp = side_wp + + # Spawn the actor + blocker_actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', side_wp.transform, 'scenario', attribute_filter=self._attributes) + if not blocker_actor: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't spawn an actor") + blocker_actor.apply_control(carla.VehicleControl(hand_brake=True)) + + blocker_actor.set_simulate_physics(False) + blocker_actor.set_location(side_wp.transform.location + carla.Location(z=-500)) + self._side_transforms.append([blocker_actor, side_wp.transform]) + self.other_actors.append(blocker_actor) + + # Move to the front + next_wps = blocker_wp.next(self._vehicle_gap) + if not next_wps: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + blocker_wp = next_wps[0] + + self._collision_wp = blocker_wp + + # Get the cut in behavior + self._plan, dist, step = ([], 0, 5) + next_wp = self._collision_wp + while dist < self._adversary_end_distance: + next_wps = next_wp.next(step) + if not next_wps: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + next_wp = next_wps[0] + self._plan.append([next_wp, RoadOption.STRAIGHT]) + + dist += step + + # Spawn the cut in vehicle + side_wp = blocker_wp.get_left_lane() if self._direction == 'left' else blocker_wp.get_right_lane() + if not side_wp: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + + self._adversary_actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', side_wp.transform, 'scenario', attribute_filter=self._attributes) + if not self._adversary_actor: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't spawn an actor") + + self._adversary_actor.set_simulate_physics(False) + self._adversary_actor.set_location(side_wp.transform.location + carla.Location(z=-500)) + self._side_transforms.append([self._adversary_actor, side_wp.transform]) + self.other_actors.append(self._adversary_actor) + + # This starts the engine, to allow the adversary to instantly move + self._adversary_actor.apply_control(carla.VehicleControl(throttle=1.0, brake=1.0)) + + # Move to the front + next_wps = blocker_wp.next(self._vehicle_gap) + if not next_wps: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + blocker_wp = next_wps[0] + + # Spawn the vehicles in front of the cut in one + for i in range(self._front_vehicles): + # Move to the side + side_wp = blocker_wp.get_left_lane() if self._direction == 'left' else blocker_wp.get_right_lane() + if not side_wp: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + + # Spawn the actor + blocker_actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', side_wp.transform, 'scenario', attribute_filter=self._attributes) + if not blocker_actor: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't spawn an actor") + blocker_actor.apply_control(carla.VehicleControl(hand_brake=True)) + + blocker_actor.set_simulate_physics(False) + blocker_actor.set_location(side_wp.transform.location + carla.Location(z=-500)) + self._side_transforms.append([blocker_actor, side_wp.transform]) + self.other_actors.append(blocker_actor) + + # Move to the front + next_wps = blocker_wp.next(self._vehicle_gap) + if not next_wps: + for actor in self.other_actors: + actor.destroy() + raise ValueError("Couldn't find a proper position for the cut in vehicle") + blocker_wp = next_wps[0] + + def _create_behavior(self): + """ + After invoking this scenario, a parked vehicle will wait for the ego to + be close-by, merging into its lane, forcing it to break. + """ + sequence = py_trees.composites.Sequence(name="StaticCutIn") + if self.route_mode: + total_dist = self._blocker_distance + total_dist += self._vehicle_gap * (self._back_vehicles + self._front_vehicles + 1) + sequence.add_child(LeaveSpaceInFront(total_dist)) + + sequence.add_child(BatchActorTransformSetter(self._side_transforms)) + + collision_location = self._collision_wp.transform.location + + # Wait until ego is close to the adversary + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._reaction_time, collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + + sequence.add_child(trigger_adversary) + if self.route_mode: + sequence.add_child(ChangeRoadBehavior(extra_space=self._extra_space)) + + if self.route_mode: + sequence.add_child(RemoveRoadLane(self._side_wp)) + + cut_in_behavior = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="CutIn") + cut_in_direction = 'right' if self._direction == 'left' else 'left' + + cut_in_movement = py_trees.composites.Sequence() + cut_in_movement.add_child(CutIn( + self._adversary_actor, self.ego_vehicles[0], cut_in_direction, change_time=3, other_lane_time=2)) + cut_in_movement.add_child(BasicAgentBehavior( + self._adversary_actor, plan=self._plan, target_speed=self._speed)) + + cut_in_behavior.add_child(cut_in_movement) + cut_in_behavior.add_child(Idle(30)) # Timeout in case a collision happened + + sequence.add_child(cut_in_behavior) + + for actor in self.other_actors: + sequence.add_child(ActorDestroy(actor)) + + if self.route_mode: + sequence.add_child(ChangeRoadBehavior(extra_space=0)) + sequence.add_child(ReAddRoadLane(1 if self._direction == 'right' else -1)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/follow_leading_vehicle.py b/srunner/scenarios/follow_leading_vehicle.py index b87a97912..5f600611d 100644 --- a/srunner/scenarios/follow_leading_vehicle.py +++ b/srunner/scenarios/follow_leading_vehicle.py @@ -27,8 +27,7 @@ ActorDestroy, KeepVelocity, StopVehicle, - WaypointFollower, - Idle) + WaypointFollower) from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToVehicle, InTriggerDistanceToNextIntersection, @@ -38,8 +37,6 @@ from srunner.scenarios.basic_scenario import BasicScenario from srunner.tools.scenario_helper import get_waypoint_in_distance -from srunner.tools.background_manager import Scenario2Manager - class FollowLeadingVehicle(BasicScenario): @@ -310,64 +307,3 @@ def __del__(self): Remove all actors upon deletion """ self.remove_all_actors() - - -class FollowLeadingVehicleRoute(BasicScenario): - - """ - This class is the route version of FollowLeadingVehicle where the backgrounda activity is used, - instead of spawning a specific vehicle and making it brake. - - This is a single ego vehicle scenario - """ - - timeout = 120 # Timeout of scenario in seconds - - def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, - timeout=60): - """ - Setup all relevant parameters and create scenario - """ - self.timeout = timeout - self._stop_duration = 15 - self._end_time_condition = 30 - - super(FollowLeadingVehicleRoute, self).__init__("FollowLeadingVehicleRoute", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) - - def _initialize_actors(self, config): - """ - Custom initialization - """ - pass - - def _create_behavior(self): - """ - Uses the Background Activity API to force a hard break on the vehicles in front of the actor, - then waits for a bit to check if the actor has collided. - """ - sequence = py_trees.composites.Sequence("FollowLeadingVehicleRoute") - sequence.add_child(Scenario2Manager(self._stop_duration)) - sequence.add_child(Idle(self._end_time_condition)) - - return sequence - - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - criteria = [] - criteria.append(CollisionTest(self.ego_vehicles[0])) - - return criteria - - def __del__(self): - """ - Remove all actors upon deletion - """ - self.remove_all_actors() diff --git a/srunner/scenarios/green_traffic_light.py b/srunner/scenarios/green_traffic_light.py new file mode 100644 index 000000000..c05fdd111 --- /dev/null +++ b/srunner/scenarios/green_traffic_light.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Sets the ego incoming traffic light to green. Support scenario at routes +to let the ego gather speed +""" + +import py_trees + +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import TrafficLightFreezer +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitEndIntersection +from srunner.scenarios.basic_scenario import BasicScenario + + +class PriorityAtJunction(BasicScenario): + """ + Sets the ego incoming traffic light to green. Support scenario at routes + to let the ego gather speed + """ + + timeout = 80 # Timeout of scenario in seconds + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=80): + """ + Setup all relevant parameters and create scenario + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self._tl_dict = {} + + self.timeout = timeout + super().__init__("PriorityAtJunction", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Get the junction and traffic lights + """ + ego_location = config.trigger_points[0].location + self._ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) + + # Get the junction + starting_wp = self._ego_wp + ego_junction_dist = 0 + while not starting_wp.is_junction: + starting_wps = starting_wp.next(1.0) + if len(starting_wps) == 0: + raise ValueError("Failed to find junction") + starting_wp = starting_wps[0] + ego_junction_dist += 1 + self._junction = starting_wp.get_junction() + + self._get_traffic_lights(self._junction, ego_junction_dist) + + def _get_traffic_lights(self, junction, junction_dist): + """Get the traffic light of the junction, mapping their states""" + tls = self._world.get_traffic_lights_in_junction(junction.id) + if not tls: + raise ValueError("No traffic lights found, nothing to do here") + + ego_landmark = self._ego_wp.get_landmarks_of_type(junction_dist + 1, "1000001")[0] + ego_tl = self._world.get_traffic_light(ego_landmark) + for tl in tls: + self._tl_dict[tl] = carla.TrafficLightState.Green if tl.id == ego_tl.id else carla.TrafficLightState.Red + + def _create_behavior(self): + """ + Freeze the traffic lights until the ego has exited the junction + """ + root = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + root.add_child(WaitEndIntersection(self.ego_vehicles[0], self._junction.id)) + root.add_child(TrafficLightFreezer(self._tl_dict)) + return root + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + return [] + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/hard_break.py b/srunner/scenarios/hard_break.py new file mode 100644 index 000000000..13f8e77f4 --- /dev/null +++ b/srunner/scenarios/hard_break.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2022 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Hard break scenario: + +The scenario spawn a vehicle in front of the ego that drives for a while before +suddenly hard breaking, forcing the ego to avoid the collision +""" + +import py_trees + +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import Idle +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import DriveDistance +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import StopFrontVehicles, StartFrontVehicles + + +class HardBreakRoute(BasicScenario): + + """ + This class uses the is the Background Activity at routes to create a hard break scenario. + + This is a single ego vehicle scenario + """ + + timeout = 120 # Timeout of scenario in seconds + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=60): + """ + Setup all relevant parameters and create scenario + """ + self.timeout = timeout + self._stop_duration = 10 + self.end_distance = 15 + + super().__init__("HardBreak", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + pass + + def _create_behavior(self): + """ + Uses the Background Activity to force a hard break on the vehicles in front of the actor, + then waits for a bit to check if the actor has collided. After a set duration, + the front vehicles will resume their movement + """ + sequence = py_trees.composites.Sequence("HardBreak") + sequence.add_child(StopFrontVehicles()) + sequence.add_child(Idle(self._stop_duration)) + sequence.add_child(StartFrontVehicles()) + sequence.add_child(DriveDistance(self.ego_vehicles[0], self.end_distance)) + + return sequence + + def _create_test_criteria(self): + """ + Empty, the route already has a collision criteria + """ + return [] + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/highway_cut_in.py b/srunner/scenarios/highway_cut_in.py new file mode 100644 index 000000000..1a0fac4f3 --- /dev/null +++ b/srunner/scenarios/highway_cut_in.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenarios in which another (opposite) vehicle 'illegally' takes +priority, e.g. by running a red traffic light. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + ActorTransformSetter, + SyncArrivalWithAgent, + CutIn) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import HandleJunctionScenario + +from srunner.tools.scenario_helper import generate_target_waypoint + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + +class HighwayCutIn(BasicScenario): + """ + This class holds everything required for a scenario in which another vehicle runs a red light + in front of the ego, forcing it to react. This vehicles are 'special' ones such as police cars, + ambulances or firetrucks. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._same_lane_time = 0.3 + self._other_lane_time = 3 + self._change_time = 2 + self._speed_perc = 80 + self._cut_in_distance = 10 + self._extra_space = 170 + + self._start_location = convert_dict_to_location(config.other_parameters['other_actor_location']) + + super().__init__("HighwayCutIn", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + self._other_waypoint = self._map.get_waypoint(self._start_location) + self._other_transform = self._other_waypoint.transform + + self._cut_in_vehicle = CarlaDataProvider.request_new_actor( + 'vehicle.*', self._other_transform, rolename='scenario', + attribute_filter={'base_type': 'car', 'has_lights': True} + ) + self.other_actors.append(self._cut_in_vehicle) + + # Move below ground + self._cut_in_vehicle.set_location(self._other_transform.location - carla.Location(z=100)) + self._cut_in_vehicle.set_simulate_physics(False) + + + def _create_behavior(self): + """ + Hero vehicle is entering a junction in an urban area, at a signalized intersection, + while another actor runs a red lift, forcing the ego to break. + """ + behavior = py_trees.composites.Sequence("HighwayCutIn") + + if self.route_mode: + behavior.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=False, + remove_entries=[self._other_waypoint], + remove_exits=[], + stop_entries=False, + extend_road_exit=self._extra_space + )) + behavior.add_child(ActorTransformSetter(self._cut_in_vehicle, self._other_transform)) + + # Sync behavior + target_wp = generate_target_waypoint(self._other_waypoint) + front_wps = target_wp.next(self._cut_in_distance) + if not front_wps: + raise ValueError("Couldn't find a waypoint to perform the cut in") + target_wp = front_wps[0] + + trigger_wp = self._map.get_waypoint(self.config.trigger_points[0].location) + reference_wp = generate_target_waypoint(trigger_wp) + behavior.add_child(SyncArrivalWithAgent( + self._cut_in_vehicle, self.ego_vehicles[0], target_wp.transform, reference_wp.transform, 5)) + + # Cut in + behavior.add_child(CutIn( + self._cut_in_vehicle, self.ego_vehicles[0], 'left', self._speed_perc, + self._same_lane_time, self._other_lane_time, self._change_time, name="Cut_in") + ) + behavior.add_child(ActorDestroy(self._cut_in_vehicle)) + return behavior + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/invading_turn.py b/srunner/scenarios/invading_turn.py new file mode 100644 index 000000000..cda71841e --- /dev/null +++ b/srunner/scenarios/invading_turn.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenario in which the ego is about to turn right +when a vehicle coming from the opposite lane invades the ego's lane, forcing the ego to move right to avoid a possible collision. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (InvadingActorFlow, + ScenarioTimeout, + ActorDestroy, + BatchActorTransformSetter) +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitUntilInFrontPosition +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import RemoveRoadLane, ChangeOppositeBehavior, ReAddRoadLane + + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + + +class InvadingTurn(BasicScenario): + """ + This class holds everything required for a scenario in which the ego is about to turn right + when a vehicle coming from the opposite lane invades the ego's lane, + forcing the ego to move right to avoid a possible collision. + + This scenario is expected to take place on a road that has only one lane in each direction. + """ + + def __init__(self, world, ego_vehicles, config, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._map.get_waypoint( + self._trigger_location) + + self._flow_frequency = 40 # m + self._source_dist = 30 # Distance between source and end point + + self._check_distance = 50 + + self._distance = get_value_parameter(config, 'distance', float, 100) + self._offset = get_value_parameter(config, 'offset', float, 0.25) # meters invaded in the opposite direction + self._scenario_timeout = 240 + + self._obstacle_transforms = [] + + super().__init__("InvadingTurn", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + # Spawn adversary actor + next_wps = self._reference_waypoint.next(self._distance + self._source_dist) + if not next_wps: + raise ValueError("Couldn't find the source location for the actor flow") + self._forward_wp = next_wps[0] + self._source_wp = self._forward_wp.get_left_lane() + if not self._source_wp: + raise ValueError("Couldn't find the source location for the actor flow") + + self._sink_wp = self._reference_waypoint.get_left_lane() + if not self._sink_wp: + raise ValueError("Couldn't find the sink location for the actor flow") + + # Lane offset + self._offset_constant = 0.7 # Ideally, half the vehicle lane width + self._true_offset = self._offset + self._sink_wp.lane_width / 2 - self._offset_constant + self._true_offset *= -1 # Cause left direction + + self._create_obstacle() + + def _create_obstacle(self): + + next_wp = self._source_wp.next(10)[0] + obstacle_distance = 0.5 * self._distance + dist = 0 + while dist < obstacle_distance: + next_wp = next_wp.next(5)[0] + + displacement = 0.8 * next_wp.lane_width / 2 + r_vec = next_wp.transform.get_right_vector() + spawn_transform = next_wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=0.3) + + cone = CarlaDataProvider.request_new_actor('*constructioncone*', spawn_transform) + self.other_actors.append(cone) + + self._obstacle_transforms.append([cone, spawn_transform]) + + transform = carla.Transform(spawn_transform.location, spawn_transform.rotation) + transform.location.z -= 200 + cone.set_transform(transform) + cone.set_simulate_physics(False) + + dist += 5 + + self._obstacle_transforms.reverse() # So that the closest cones are spawned first + + def _create_behavior(self): + """ + The adversary vehicle will go to the target place while invading another lane. + """ + sequence = py_trees.composites.Sequence("InvadingTurn") + + if self.route_mode: + sequence.add_child(RemoveRoadLane(self._reference_waypoint)) + sequence.add_child(ChangeOppositeBehavior(active=False)) + + sequence.add_child(BatchActorTransformSetter(self._obstacle_transforms)) + + main_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + main_behavior.add_child(InvadingActorFlow( + self._source_wp, self._sink_wp, self.ego_vehicles[0], self._flow_frequency, offset=self._true_offset)) + + main_behavior.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._forward_wp.transform, True, self._check_distance)) + main_behavior.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + sequence.add_child(main_behavior) + if self.route_mode: + sequence.add_child(ReAddRoadLane(0)) + sequence.add_child(ChangeOppositeBehavior(active=True)) + + for actor in self.other_actors: + sequence.add_child(ActorDestroy(actor)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/junction_crossing_route.py b/srunner/scenarios/junction_crossing_route.py deleted file mode 100644 index d8529d884..000000000 --- a/srunner/scenarios/junction_crossing_route.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2018-2020 Intel Corporation -# -# This work is licensed under the terms of the MIT license. -# For a copy, see . - -""" -All intersection related scenarios that are part of a route. -""" - -from __future__ import print_function - -import py_trees - -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import TrafficLightManipulator - -from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, DrivenDistanceTest, MaxVelocityTest -from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import DriveDistance, WaitEndIntersection -from srunner.scenarios.basic_scenario import BasicScenario - - -class SignalJunctionCrossingRoute(BasicScenario): - - """ - At routes, these scenarios are simplified, as they can be triggered making - use of the background activity. To ensure interactions with this background - activity, the traffic lights are modified, setting two of them to green - """ - - # ego vehicle parameters - _ego_max_velocity_allowed = 20 # Maximum allowed velocity [m/s] - _ego_expected_driven_distance = 50 # Expected driven distance [m] - _ego_distance_to_drive = 20 # Allowed distance to drive - - _traffic_light = None - - # Depending on the route, decide which traffic lights can be modified - - def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, - timeout=180): - """ - Setup all relevant parameters and create scenario - and instantiate scenario manager - """ - # Timeout of scenario in seconds - self.timeout = timeout - self.subtype = config.subtype - - super(SignalJunctionCrossingRoute, self).__init__("SignalJunctionCrossingRoute", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) - - def _initialize_actors(self, config): - """ - Custom initialization - """ - - def _create_behavior(self): - """ - Scenario behavior: - When close to an intersection, the traffic lights will turn green for - both the ego_vehicle and another lane, allowing the background activity - to "run" their red light, creating scenarios 7, 8 and 9. - - If this does not happen within 120 seconds, a timeout stops the scenario - """ - - # Changes traffic lights - traffic_hack = TrafficLightManipulator(self.ego_vehicles[0], self.subtype) - - # finally wait that ego vehicle drove a specific distance - wait = DriveDistance( - self.ego_vehicles[0], - self._ego_distance_to_drive, - name="DriveDistance") - - # Build behavior tree - sequence = py_trees.composites.Sequence("SignalJunctionCrossingRoute") - sequence.add_child(traffic_hack) - sequence.add_child(wait) - - return sequence - - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - criteria = [] - - max_velocity_criterion = MaxVelocityTest( - self.ego_vehicles[0], - self._ego_max_velocity_allowed, - optional=True) - collision_criterion = CollisionTest(self.ego_vehicles[0]) - driven_distance_criterion = DrivenDistanceTest( - self.ego_vehicles[0], - self._ego_expected_driven_distance) - - criteria.append(max_velocity_criterion) - criteria.append(collision_criterion) - criteria.append(driven_distance_criterion) - - return criteria - - def __del__(self): - """ - Remove all actors and traffic lights upon deletion - """ - self._traffic_light = None - self.remove_all_actors() - - -class NoSignalJunctionCrossingRoute(BasicScenario): - - """ - At routes, these scenarios are simplified, as they can be triggered making - use of the background activity. For unsignalized intersections, just wait - until the ego_vehicle has left the intersection. - """ - - # ego vehicle parameters - _ego_max_velocity_allowed = 20 # Maximum allowed velocity [m/s] - _ego_expected_driven_distance = 50 # Expected driven distance [m] - _ego_distance_to_drive = 20 # Allowed distance to drive - - def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, - timeout=180): - """ - Setup all relevant parameters and create scenario - and instantiate scenario manager - """ - # Timeout of scenario in seconds - self.timeout = timeout - - super(NoSignalJunctionCrossingRoute, self).__init__("NoSignalJunctionCrossingRoute", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) - - def _initialize_actors(self, config): - """ - Custom initialization - """ - pass - - def _create_behavior(self): - """ - Scenario behavior: - When close to an intersection, the traffic lights will turn green for - both the ego_vehicle and another lane, allowing the background activity - to "run" their red light. - - If this does not happen within 120 seconds, a timeout stops the scenario - """ - # finally wait that ego vehicle drove a specific distance - wait = WaitEndIntersection( - self.ego_vehicles[0], - name="WaitEndIntersection") - end_condition = DriveDistance( - self.ego_vehicles[0], - self._ego_distance_to_drive, - name="DriveDistance") - - # Build behavior tree - sequence = py_trees.composites.Sequence("NoSignalJunctionCrossingRoute") - sequence.add_child(wait) - sequence.add_child(end_condition) - - return sequence - - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - criteria = [] - criteria.append(CollisionTest(self.ego_vehicles[0])) - return criteria - - def __del__(self): - """ - Remove all actors and traffic lights upon deletion - """ - self.remove_all_actors() diff --git a/srunner/scenarios/master_scenario.py b/srunner/scenarios/master_scenario.py deleted file mode 100644 index 87fc4ff05..000000000 --- a/srunner/scenarios/master_scenario.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python - -# -# This work is licensed under the terms of the MIT license. -# For a copy, see . - -""" -Basic CARLA Autonomous Driving training scenario -""" - -import py_trees - -from srunner.scenarioconfigs.route_scenario_configuration import RouteConfiguration -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import Idle -from srunner.scenariomanager.scenarioatomics.atomic_criteria import (CollisionTest, - InRouteTest, - RouteCompletionTest, - OutsideRouteLanesTest, - RunningRedLightTest, - RunningStopTest, - ActorSpeedAboveThresholdTest) -from srunner.scenarios.basic_scenario import BasicScenario - - -class MasterScenario(BasicScenario): - - """ - Implementation of a Master scenario that controls the route. - - This is a single ego vehicle scenario - """ - - radius = 10.0 # meters - - def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, - timeout=300): - """ - Setup all relevant parameters and create scenario - """ - self.config = config - self.route = None - # Timeout of scenario in seconds - self.timeout = timeout - - if hasattr(self.config, 'route'): - self.route = self.config.route - else: - raise ValueError("Master scenario must have a route") - - super(MasterScenario, self).__init__("MasterScenario", ego_vehicles=ego_vehicles, config=config, - world=world, debug_mode=debug_mode, - terminate_on_failure=True, criteria_enable=criteria_enable) - - def _create_behavior(self): - """ - Basic behavior do nothing, i.e. Idle - """ - - # Build behavior tree - sequence = py_trees.composites.Sequence("MasterScenario") - idle_behavior = Idle() - sequence.add_child(idle_behavior) - - return sequence - - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - - if isinstance(self.route, RouteConfiguration): - route = self.route.data - else: - route = self.route - - collision_criterion = CollisionTest(self.ego_vehicles[0], terminate_on_failure=False) - - route_criterion = InRouteTest(self.ego_vehicles[0], - route=route, - offroad_max=30, - terminate_on_failure=True) - - completion_criterion = RouteCompletionTest(self.ego_vehicles[0], route=route) - - outsidelane_criterion = OutsideRouteLanesTest(self.ego_vehicles[0], route=route) - - red_light_criterion = RunningRedLightTest(self.ego_vehicles[0]) - - stop_criterion = RunningStopTest(self.ego_vehicles[0]) - - blocked_criterion = ActorSpeedAboveThresholdTest(self.ego_vehicles[0], - speed_threshold=0.1, - below_threshold_max_time=90.0, - terminate_on_failure=True) - - parallel_criteria = py_trees.composites.Parallel("group_criteria", - policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) - - parallel_criteria.add_child(completion_criterion) - parallel_criteria.add_child(collision_criterion) - parallel_criteria.add_child(route_criterion) - parallel_criteria.add_child(outsidelane_criterion) - parallel_criteria.add_child(red_light_criterion) - parallel_criteria.add_child(stop_criterion) - parallel_criteria.add_child(blocked_criterion) - - return parallel_criteria - - def __del__(self): - """ - Remove all actors upon deletion - """ - self.remove_all_actors() diff --git a/srunner/scenarios/no_signal_junction_crossing.py b/srunner/scenarios/no_signal_junction_crossing.py index 0c2496571..0add82905 100644 --- a/srunner/scenarios/no_signal_junction_crossing.py +++ b/srunner/scenarios/no_signal_junction_crossing.py @@ -21,7 +21,7 @@ KeepVelocity, StopVehicle) from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest -from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import InTriggerRegion +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import InTriggerRegion, DriveDistance, WaitEndIntersection from srunner.scenarios.basic_scenario import BasicScenario @@ -162,3 +162,51 @@ def __del__(self): Remove all actors upon deletion """ self.remove_all_actors() + + +class NoSignalJunctionCrossingRoute(BasicScenario): + + """ + At routes, these scenarios are simplified, as they can be triggered making + use of the background activity. For unsignalized intersections, just wait + until the ego_vehicle has left the intersection. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + # Timeout of scenario in seconds + self.timeout = timeout + self._end_distance = 50 + + super(NoSignalJunctionCrossingRoute, self).__init__("NoSignalJunctionCrossingRoute", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _create_behavior(self): + """ + Just wait for the ego to exit the junction, for route the BackgroundActivity already does all the job + """ + sequence = py_trees.composites.Sequence("UnSignalizedJunctionCrossingRoute") + sequence.add_child(WaitEndIntersection(self.ego_vehicles[0])) + sequence.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + return [] + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/object_crash_intersection.py b/srunner/scenarios/object_crash_intersection.py index da11294f9..8c1d65e00 100644 --- a/srunner/scenarios/object_crash_intersection.py +++ b/srunner/scenarios/object_crash_intersection.py @@ -18,27 +18,29 @@ from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, HandBrakeVehicle, - KeepVelocity) + KeepVelocity, + ActorTransformSetter, + MovePedestrianWithEgo) from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, InTimeToArrivalToLocation, - WaitEndIntersection, DriveDistance) from srunner.scenarios.basic_scenario import BasicScenario -from srunner.tools.scenario_helper import generate_target_waypoint, generate_target_waypoint_in_route +from srunner.tools.scenario_helper import (generate_target_waypoint, + generate_target_waypoint_in_route, + get_same_dir_lanes, + get_opposite_dir_lanes) -from srunner.tools.background_manager import Scenario4Manager +from srunner.tools.background_manager import LeaveCrossingSpace -def get_sidewalk_transform(waypoint): +def get_sidewalk_transform(waypoint, offset): """ Processes the waypoint transform to find a suitable spawning one at the sidewalk. It first rotates the transform so that it is pointing towards the road and then moves a bit to the side waypoint that aren't part of sidewalks, as they might be invading the road """ - offset = {"yaw": -90, "z": 0.2, "k": 1.5} - new_rotation = waypoint.transform.rotation new_rotation.yaw += offset['yaw'] @@ -74,9 +76,9 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals self._wmap = CarlaDataProvider.get_map() self._trigger_location = config.trigger_points[0].location self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) - self._ego_route = CarlaDataProvider.get_ego_vehicle_route() + self._ego_route = config.route - self._start_distance = 10 + self._start_distance = 11 self._spawn_dist = self._start_distance self._number_of_attempts = 6 self._retry_dist = 0.4 @@ -84,11 +86,13 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals self._adversary_transform = None self._collision_wp = None - self._adversary_speed = 4.0 # Speed of the adversary [m/s] - self._reaction_time = 0.5 # Time the agent has to react to avoid the collision [s] + self._adversary_speed = 1.8 # Speed of the adversary [m/s] + self._reaction_time = 1.8 # Time the agent has to react to avoid the collision [s] self._min_trigger_dist = 6.0 # Min distance to the collision location that triggers the adversary [m] self._ego_end_distance = 40 + self._offset = {"yaw": 270, "z": 0.2, "k": 1.5} + self.timeout = timeout super(BaseVehicleTurning, self).__init__( name, ego_vehicles, config, world, debug_mode, criteria_enable=criteria_enable) @@ -115,6 +119,8 @@ def _initialize_actors(self, config): waypoint = self._get_target_waypoint() move_dist = self._start_distance while self._number_of_attempts > 0: + parking_location = None + # Move to the front waypoint = waypoint.next(move_dist)[0] self._collision_wp = waypoint @@ -126,9 +132,11 @@ def _initialize_actors(self, config): if right_wp is None: break # No more right lanes sidewalk_waypoint = right_wp + if sidewalk_waypoint.lane_type == carla.LaneType.Parking: + parking_location = sidewalk_waypoint.transform.location # Get the adversary transform and spawn it - self._adversary_transform = get_sidewalk_transform(sidewalk_waypoint) + self._adversary_transform = get_sidewalk_transform(sidewalk_waypoint, self._offset) adversary = CarlaDataProvider.request_new_actor('vehicle.diamondback.century', self._adversary_transform) if adversary is None: self._number_of_attempts -= 1 @@ -140,7 +148,10 @@ def _initialize_actors(self, config): break if self._number_of_attempts == 0: - raise Exception("Couldn't find viable position for the adversary actor") + raise ValueError("Couldn't find viable position for the adversary") + + if parking_location: + self.parking_slots.append(parking_location) if isinstance(adversary, carla.Vehicle): adversary.apply_control(carla.VehicleControl(hand_brake=True)) @@ -155,39 +166,30 @@ def _create_behavior(self): continue driving after the road is clear.If this does not happen within 90 seconds, a timeout stops the scenario. """ - sequence = py_trees.composites.Sequence() + sequence = py_trees.composites.Sequence(name="CrossingActorIntersection") collision_location = self._collision_wp.transform.location collision_distance = collision_location.distance(self._adversary_transform.location) collision_duration = collision_distance / self._adversary_speed - collision_time_trigger = collision_duration + self._reaction_time - - # On trigger behavior - if self._ego_route is not None: - sequence.add_child(Scenario4Manager(self._spawn_dist)) - - # First waiting behavior - sequence.add_child(WaitEndIntersection(self.ego_vehicles[0])) # Adversary trigger behavior trigger_adversary = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") trigger_adversary.add_child(InTimeToArrivalToLocation( - self.ego_vehicles[0], collision_time_trigger, collision_location)) + self.ego_vehicles[0], self._reaction_time, collision_location)) trigger_adversary.add_child(InTriggerDistanceToLocation( self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + sequence.add_child(trigger_adversary) sequence.add_child(HandBrakeVehicle(self.other_actors[0], False)) # Move the adversary. speed_duration = 2.0 * collision_duration speed_distance = 2.0 * collision_distance + if self.route_mode: + sequence.add_child(LeaveCrossingSpace(self._collision_wp)) sequence.add_child(KeepVelocity( - self.other_actors[0], - self._adversary_speed, - True, - speed_duration, - speed_distance, - name="AdversaryCrossing") + self.other_actors[0], self._adversary_speed, True, + speed_duration, speed_distance, name="AdversaryCrossing") ) # Remove everything @@ -201,11 +203,7 @@ def _create_test_criteria(self): A list of all test criteria will be created that is later used in parallel behavior tree. """ - criteria = [] - collision_criterion = CollisionTest(self.ego_vehicles[0]) - - criteria.append(collision_criterion) - return criteria + return [CollisionTest(self.ego_vehicles[0])] def __del__(self): """ @@ -260,3 +258,186 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals self._subtype = 'route' super(VehicleTurningRoute, self).__init__( world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout, "VehicleTurningRoute") + + def _create_test_criteria(self): + """ + Empty, the route already has a collision criteria + """ + return [] + + +class VehicleTurningRoutePedestrian(BasicScenario): + + """ + This class holds everything required for a simple object crash + with prior vehicle action involving a vehicle and a cyclist. + The ego vehicle is passing through a road and encounters + a cyclist after taking a turn. + + This is a single ego vehicle scenario + """ + _subtype = None + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=60, name="VehicleTurningRoutePedestrian"): + """ + Setup all relevant parameters and create scenario + """ + + self._wmap = CarlaDataProvider.get_map() + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) + self._ego_route = config.route + + self._collision_wp = None + self._adversary_speed = 1.8 # Speed of the adversary [m/s] + self._reaction_time = 2.2 # Time the agent has to react to avoid the collision [s] + self._min_trigger_dist = 6.0 # Min distance to the collision location that triggers the adversary [m] + self._ego_end_distance = 40 + + self._offset = {"yaw": 270, "z": 1.2, "k": 1.5} + + self.timeout = timeout + super().__init__(name, ego_vehicles, config, world, debug_mode, criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + # Get the waypoint right after the junction + parking_location = None + waypoint = generate_target_waypoint_in_route(self._reference_waypoint, self._ego_route) + self._collision_wp = waypoint.next(0.5)[0] # Some wps are still part of the junction + + # Get the right waypoint at the sidewalk + same_dir_wps = get_same_dir_lanes(self._collision_wp) + right_wp = same_dir_wps[0] + while right_wp.lane_type != carla.LaneType.Sidewalk: + side_wp = right_wp.get_right_lane() + if side_wp is None: + break + right_wp = side_wp + if right_wp.lane_type == carla.LaneType.Parking: + parking_location = right_wp.transform.location + + # Get the left waypoint at the sidewalk + other_dir_wps = get_opposite_dir_lanes(self._collision_wp) + if other_dir_wps: + # With opposite lane + left_wp = other_dir_wps[-1] + while left_wp.lane_type != carla.LaneType.Sidewalk: + side_wp = left_wp.get_right_lane() + if side_wp is None: + break + left_wp = side_wp + if left_wp.lane_type == carla.LaneType.Parking: + parking_location = left_wp.transform.location + else: + # Without opposite lane + self._offset['yaw'] = 90 + left_wp = same_dir_wps[-1] + while left_wp.lane_type != carla.LaneType.Sidewalk: + side_wp = left_wp.get_left_lane() + if side_wp is None: + break + left_wp = side_wp + if left_wp.lane_type == carla.LaneType.Parking: + parking_location = left_wp.transform.location + + self._adversary_distance = right_wp.transform.location.distance(left_wp.transform.location) + + entry_vec = self._reference_waypoint.transform.get_forward_vector() + exit_vec = waypoint.transform.get_forward_vector() + cross_prod = entry_vec.cross(exit_vec) + spawn_wp = right_wp if cross_prod.z < 0 else left_wp + + # Get the adversary transform and spawn it + self._spawn_transform = get_sidewalk_transform(spawn_wp, self._offset) + adversary = CarlaDataProvider.request_new_actor('walker.*', self._spawn_transform) + if adversary is None: + raise ValueError("Couldn't spawn adversary") + + adversary.set_location(self._spawn_transform.location + carla.Location(z=-200)) + adversary = self._replace_walker(adversary) + + if parking_location: + self.parking_slots.append(parking_location) + + self.other_actors.append(adversary) + + def _create_behavior(self): + """ + """ + sequence = py_trees.composites.Sequence(name="VehicleTurningRoutePedestrian") + sequence.add_child(ActorTransformSetter(self.other_actors[0], self._spawn_transform, True)) + + collision_location = self._collision_wp.transform.location + + # Adversary trigger behavior + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._reaction_time, collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + + sequence.add_child(trigger_adversary) + if self.route_mode: + sequence.add_child(LeaveCrossingSpace(self._collision_wp)) + + # Move the adversary. + speed_distance = self._adversary_distance + speed_duration = self._adversary_distance / self._adversary_speed + sequence.add_child(KeepVelocity( + self.other_actors[0], self._adversary_speed, True, + speed_duration, speed_distance, name="AdversaryCrossing") + ) + + # Remove everything + sequence.add_child(ActorDestroy(self.other_actors[0], name="DestroyAdversary")) + sequence.add_child(DriveDistance(self.ego_vehicles[0], self._ego_end_distance, name="EndCondition")) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + return [CollisionTest(self.ego_vehicles[0])] + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() + + # TODO: Pedestrian have an issue with large maps were setting them to dormant breaks them, + # so all functions below are meant to patch it until the fix is done + def _replace_walker(self, adversary): + """As the adversary is probably, replace it with another one""" + type_id = adversary.type_id + adversary.destroy() + spawn_transform = self.ego_vehicles[0].get_transform() + spawn_transform.location.z -= 50 + adversary = CarlaDataProvider.request_new_actor(type_id, spawn_transform) + if not adversary: + raise ValueError("Couldn't spawn the walker substitute") + adversary.set_simulate_physics(False) + adversary.set_location(spawn_transform.location + carla.Location(z=-50)) + return adversary + + def _setup_scenario_trigger(self, config): + """Normal scenario trigger but in parallel, a behavior that ensures the pedestrian stays active""" + trigger_tree = super()._setup_scenario_trigger(config) + + if not self.route_mode: + return trigger_tree + + parallel = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="ScenarioTrigger") + + parallel.add_child(MovePedestrianWithEgo(self.ego_vehicles[0], self.other_actors[0], 100)) + + parallel.add_child(trigger_tree) + return parallel diff --git a/srunner/scenarios/object_crash_vehicle.py b/srunner/scenarios/object_crash_vehicle.py index 14316d511..485705ade 100644 --- a/srunner/scenarios/object_crash_vehicle.py +++ b/srunner/scenarios/object_crash_vehicle.py @@ -13,11 +13,14 @@ import math import py_trees import carla +from math import floor from srunner.scenariomanager.carla_data_provider import CarlaDataProvider from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, KeepVelocity, - Idle) + Idle, + ActorTransformSetter, + MovePedestrianWithEgo) from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, InTimeToArrivalToLocation, @@ -25,6 +28,15 @@ from srunner.scenarios.basic_scenario import BasicScenario from srunner.tools.scenario_helper import get_location_in_distance_from_wp +from srunner.tools.background_manager import LeaveSpaceInFront, LeaveCrossingSpace + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + class StationaryObjectCrossing(BasicScenario): @@ -63,9 +75,9 @@ def _initialize_actors(self, config): """ Custom initialization """ - _start_distance = 40 + _distance = 40 lane_width = self._reference_waypoint.lane_width - location, _ = get_location_in_distance_from_wp(self._reference_waypoint, _start_distance) + location, _ = get_location_in_distance_from_wp(self._reference_waypoint, _distance) waypoint = self._wmap.get_waypoint(location) offset = {"orientation": 270, "position": 90, "z": 0.4, "k": 0.2} position_yaw = waypoint.transform.rotation.yaw + offset['position'] @@ -95,6 +107,7 @@ def _create_behavior(self): # non leaf nodes root = py_trees.composites.Parallel( + name="StaticObstacle", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) scenario_sequence = py_trees.composites.Sequence() @@ -136,9 +149,7 @@ class DynamicObjectCrossing(BasicScenario): This is a single ego vehicle scenario """ - def __init__(self, world, ego_vehicles, config, - adversary_type='walker.*', blocker_type='static.prop.vendingmachine', - randomize=False, debug_mode=False, criteria_enable=True, timeout=60): + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=60): """ Setup all relevant parameters and create scenario """ @@ -147,25 +158,31 @@ def __init__(self, world, ego_vehicles, config, self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) self._num_lane_changes = 0 - self._start_distance = 12 self._blocker_shift = 0.9 self._retry_dist = 0.4 - self._adversary_type = adversary_type # blueprint filter of the adversary - self._blocker_type = blocker_type # blueprint filter of the blocker self._adversary_transform = None self._blocker_transform = None self._collision_wp = None - self._adversary_speed = 4.0 # Speed of the adversary [m/s] - self._reaction_time = 0.8 # Time the agent has to react to avoid the collision [s] - self._reaction_ratio = 0.12 # The higehr the number of lane changes, the smaller the reaction time + self._adversary_speed = 2.0 # Speed of the adversary [m/s] + self._crossing_angle = get_value_parameter(config, 'crossing_angle', float, 0) + self._reaction_time = 2.1 # Time the agent has to react to avoid the collision [s] + self._reaction_time += 0.1 * floor(self._crossing_angle / 5) self._min_trigger_dist = 6.0 # Min distance to the collision location that triggers the adversary [m] self._ego_end_distance = 40 self.timeout = timeout self._number_of_attempts = 6 + self._distance = get_value_parameter(config, 'distance', float, 12) + self._blocker_model = get_value_parameter(config, 'blocker_model', str, 'static.prop.vendingmachine') + if abs(self._crossing_angle) > 90: + raise ValueError("'crossing_angle' must be between -90 and 90º for the pedestrian to cross the road") + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + super(DynamicObjectCrossing, self).__init__("DynamicObjectCrossing", ego_vehicles, config, @@ -179,6 +196,9 @@ def _get_sidewalk_transform(self, waypoint, offset): It first rotates the transform so that it is pointing towards the road and then moves a bit to the side waypoint that aren't part of sidewalks, as they might be invading the road """ + if self._direction == "left": + offset['yaw'] *= -1 + offset['k'] *= -1 new_rotation = waypoint.transform.rotation new_rotation.yaw += offset['yaw'] @@ -187,7 +207,7 @@ def _get_sidewalk_transform(self, waypoint, offset): new_location = waypoint.transform.location else: right_vector = waypoint.transform.get_right_vector() - offset_dist = waypoint.lane_width * offset["k"] + offset_dist = offset["k"] offset_location = carla.Location(offset_dist * right_vector.x, offset_dist * right_vector.y) new_location = waypoint.transform.location + offset_location new_location.z += offset['z'] @@ -199,10 +219,12 @@ def _initialize_actors(self, config): Custom initialization """ # Get the waypoint in front of the ego. - move_dist = self._start_distance + move_dist = self._distance waypoint = self._reference_waypoint + while self._number_of_attempts > 0: - self._num_lane_changes = 0 + parking_location = None + self._collision_dist = 0 # Move to the front location, _ = get_location_in_distance_from_wp(waypoint, move_dist, False) @@ -212,41 +234,59 @@ def _initialize_actors(self, config): # Move to the right sidewalk_waypoint = waypoint while sidewalk_waypoint.lane_type != carla.LaneType.Sidewalk: - right_wp = sidewalk_waypoint.get_right_lane() - if right_wp is None: - break # No more right lanes - sidewalk_waypoint = right_wp - self._num_lane_changes += 1 + if self._direction == "right": + side_wp = sidewalk_waypoint.get_right_lane() + else: + side_wp = sidewalk_waypoint.get_left_lane() + if side_wp is None: + break # No more side lanes + sidewalk_waypoint = side_wp + if side_wp.lane_type == carla.LaneType.Parking: + parking_location = side_wp.transform.location - # Get the adversary transform and spawn it - offset = {"yaw": 270, "z": 0.5, "k": 1.0} - self._adversary_transform = self._get_sidewalk_transform(sidewalk_waypoint, offset) - adversary = CarlaDataProvider.request_new_actor(self._adversary_type, self._adversary_transform) - if adversary is None: + # Get the blocker transform and spawn it + offset = {"yaw": 0 if 'vehicle' in self._blocker_model else 90, "z": 0.0, "k": 1.5} + self._blocker_transform = self._get_sidewalk_transform(sidewalk_waypoint, offset) + blocker = CarlaDataProvider.request_new_actor( + self._blocker_model, self._blocker_transform, rolename="scenario no lights") + if not blocker: self._number_of_attempts -= 1 move_dist = self._retry_dist + print("Failed to spawn the blocker") continue - # Get the blocker transform and spawn it - blocker_wp = sidewalk_waypoint.previous(self._blocker_shift)[0] - offset = {"yaw": 90, "z": 0.0, "k": 1.0} - self._blocker_transform = self._get_sidewalk_transform(blocker_wp, offset) - blocker = CarlaDataProvider.request_new_actor(self._blocker_type, self._blocker_transform) - if not blocker: - adversary.destroy() + # Get the adversary transform and spawn it + walker_dist = blocker.bounding_box.extent.x + 0.5 + wps = sidewalk_waypoint.next(walker_dist) + if not wps: + raise ValueError("Couldn't find a location to spawn the adversary") + walker_wp = wps[0] + + offset = {"yaw": 270 - self._crossing_angle, "z": 1.2, "k": 1.2} + self._adversary_transform = self._get_sidewalk_transform(walker_wp, offset) + adversary = CarlaDataProvider.request_new_actor('walker.*', self._adversary_transform) + if adversary is None: + blocker.destroy() self._number_of_attempts -= 1 move_dist = self._retry_dist + print("Failed to spawn an adversary") continue - # Both actors where summoned, end + self._collision_dist += waypoint.transform.location.distance(self._adversary_transform.location) + + # Both actors were succesfully spawned, end break if self._number_of_attempts == 0: raise Exception("Couldn't find viable position for the adversary and blocker actors") - if isinstance(adversary, carla.Vehicle): - adversary.apply_control(carla.VehicleControl(hand_brake=True)) - blocker.set_simulate_physics(enabled=False) + blocker.set_simulate_physics(False) + adversary.set_location(self._adversary_transform.location + carla.Location(z=-200)) + adversary = self._replace_walker(adversary) + + if parking_location: + self.parking_slots.append(parking_location) + self.other_actors.append(adversary) self.other_actors.append(blocker) @@ -257,28 +297,31 @@ def _create_behavior(self): the cyclist starts crossing the road once the condition meets, then after 60 seconds, a timeout stops the scenario """ - sequence = py_trees.composites.Sequence() + sequence = py_trees.composites.Sequence(name="CrossingActor") + if self.route_mode: + total_dist = self._distance + 10 + sequence.add_child(LeaveSpaceInFront(total_dist)) + + sequence.add_child(ActorTransformSetter(self.other_actors[0], self._adversary_transform, True)) collision_location = self._collision_wp.transform.location - collision_distance = collision_location.distance(self._adversary_transform.location) - collision_duration = collision_distance / self._adversary_speed - reaction_time = self._reaction_time - self._reaction_ratio * self._num_lane_changes - collision_time_trigger = collision_duration + reaction_time # Wait until ego is close to the adversary trigger_adversary = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") trigger_adversary.add_child(InTimeToArrivalToLocation( - self.ego_vehicles[0], collision_time_trigger, collision_location)) + self.ego_vehicles[0], self._reaction_time, collision_location)) trigger_adversary.add_child(InTriggerDistanceToLocation( self.ego_vehicles[0], collision_location, self._min_trigger_dist)) sequence.add_child(trigger_adversary) # Move the adversary - speed_duration = 2.0 * collision_duration - speed_distance = 2.0 * collision_distance + move_distance = 2 * self._collision_dist # Cross the whole road (supposing symetry in both directions) + move_duration = move_distance / self._adversary_speed + if self.route_mode: + sequence.add_child(LeaveCrossingSpace(self._collision_wp)) sequence.add_child(KeepVelocity( self.other_actors[0], self._adversary_speed, - duration=speed_duration, distance=speed_distance, name="AdversaryCrossing")) + duration=move_duration, distance=move_distance, name="AdversaryCrossing")) # Remove everything sequence.add_child(ActorDestroy(self.other_actors[0], name="DestroyAdversary")) @@ -292,15 +335,249 @@ def _create_test_criteria(self): A list of all test criteria will be created that is later used in parallel behavior tree. """ - criteria = [] + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] - collision_criterion = CollisionTest(self.ego_vehicles[0]) - criteria.append(collision_criterion) + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() - return criteria + # TODO: Pedestrian have an issue with large maps were setting them to dormant breaks them, + # so all functions below are meant to patch it until the fix is done + def _replace_walker(self, adversary): + """As the adversary is probably, replace it with another one""" + type_id = adversary.type_id + adversary.destroy() + spawn_transform = self.ego_vehicles[0].get_transform() + spawn_transform.location.z -= 50 + adversary = CarlaDataProvider.request_new_actor(type_id, spawn_transform) + if not adversary: + raise ValueError("Couldn't spawn the walker substitute") + adversary.set_simulate_physics(False) + adversary.set_location(spawn_transform.location + carla.Location(z=-50)) + return adversary + + def _setup_scenario_trigger(self, config): + """Normal scenario trigger but in parallel, a behavior that ensures the pedestrian stays active""" + trigger_tree = super()._setup_scenario_trigger(config) + + if not self.route_mode: + return trigger_tree + + parallel = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="ScenarioTrigger") + + parallel.add_child(MovePedestrianWithEgo(self.ego_vehicles[0], self.other_actors[0], 100)) + + parallel.add_child(trigger_tree) + return parallel + + +class ParkingCrossingPedestrian(BasicScenario): + + """ + Variation of DynamicObjectCrossing but now the blocker is now a vehicle + This class holds everything required for a simple object crash + without prior vehicle action involving a vehicle and a cyclist/pedestrian, + The ego vehicle is passing through a road, + And encounters a cyclist/pedestrian crossing the road. + + This is a single ego vehicle scenario + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=60): + """ + Setup all relevant parameters and create scenario + """ + self._wmap = CarlaDataProvider.get_map() + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) + self._num_lane_changes = 0 + + self._adversary_speed = 2.0 # Speed of the adversary [m/s] + self._min_trigger_dist = 6.0 # Min distance to the collision location that triggers the adversary [m] + self._ego_end_distance = 40 + self.timeout = timeout + + self._bp_attributes = {'base_type': 'car', 'generation': 2} + + self._distance = get_value_parameter(config, 'distance', float, 12) + self._crossing_angle = get_value_parameter(config, 'crossing_angle', float, 0) + if abs(self._crossing_angle) > 90: + raise ValueError("'crossing_angle' must be between -90 and 90º for the pedestrian to cross the road") + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + # Time the agent has to react to avoid the collision [s] + self._reaction_time = 2.15 + self._reaction_time += 0.1 * floor(self._crossing_angle / 5) + + super().__init__("ParkingCrossingPedestrian", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _get_blocker_transform(self, waypoint): + """Processes the driving wp to get a waypoint at the side that looks at the road""" + if waypoint.lane_type == carla.LaneType.Sidewalk: + new_location = waypoint.transform.location + else: + vector = waypoint.transform.get_right_vector() + if self._direction == 'left': + vector *= -1 + + offset_location = carla.Location(waypoint.lane_width * vector.x, waypoint.lane_width * vector.y) + new_location = waypoint.transform.location + offset_location + new_location.z += 0.5 + + return carla.Transform(new_location, waypoint.transform.rotation) + + def _get_walker_transform(self, waypoint): + """Processes the driving wp to get a waypoint at the side that looks at the road""" + + new_rotation = waypoint.transform.rotation + new_rotation.yaw += 270 - self._crossing_angle if self._direction == 'right' else 90 + self._crossing_angle + + if waypoint.lane_type == carla.LaneType.Sidewalk: + new_location = waypoint.transform.location + else: + vector = waypoint.transform.get_right_vector() + if self._direction == 'left': + vector *= -1 + + offset_location = carla.Location(waypoint.lane_width * vector.x, waypoint.lane_width * vector.y) + new_location = waypoint.transform.location + offset_location + new_location.z += 1.2 + + return carla.Transform(new_location, new_rotation) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + # Get the adversary transform and spawn it + wps = self._reference_waypoint.next(self._distance) + if not wps: + raise ValueError("Couldn't find a location to spawn the adversary") + blocker_wp = wps[0] + + # Get the adversary transform and spawn it + self._blocker_transform = self._get_blocker_transform(blocker_wp) + self.parking_slots.append(self._blocker_transform.location) + blocker = CarlaDataProvider.request_new_actor( + 'vehicle.*', self._blocker_transform, attribute_filter=self._bp_attributes) + if blocker is None: + raise ValueError("Couldn't spawn the adversary") + self.other_actors.append(blocker) + blocker.apply_control(carla.VehicleControl(hand_brake=True)) + + walker_dist = blocker.bounding_box.extent.x + 0.5 + wps = blocker_wp.next(walker_dist) + if not wps: + raise ValueError("Couldn't find a location to spawn the adversary") + walker_wp = wps[0] + + # Get the adversary transform and spawn it + self._walker_transform = self._get_walker_transform(walker_wp) + self.parking_slots.append(self._walker_transform.location) + + walker = CarlaDataProvider.request_new_actor('walker.*', self._walker_transform) + if walker is None: + raise ValueError("Couldn't spawn the adversary") + + walker.set_location(self._walker_transform.location + carla.Location(z=-200)) + walker = self._replace_walker(walker) + + self.other_actors.append(walker) + + self._collision_wp = walker_wp + + def _create_behavior(self): + """ + After invoking this scenario, cyclist will wait for the user + controlled vehicle to enter trigger distance region, + the cyclist starts crossing the road once the condition meets, + then after 60 seconds, a timeout stops the scenario + """ + sequence = py_trees.composites.Sequence(name="ParkingCrossingPedestrian") + if self.route_mode: + total_dist = self._distance + 15 + sequence.add_child(LeaveSpaceInFront(total_dist)) + + sequence.add_child(ActorTransformSetter(self.other_actors[1], self._walker_transform, True)) + collision_location = self._collision_wp.transform.location + + # Wait until ego is close to the adversary + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._reaction_time, collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + sequence.add_child(trigger_adversary) + + # Move the adversary + distance = 8.0 # Scenario is meant to be used at a one lane - one direction road + duration = distance / self._adversary_speed + + sequence.add_child(KeepVelocity( + self.other_actors[1], self._adversary_speed, + duration=duration, distance=distance, name="AdversaryCrossing")) + + # Remove everything + sequence.add_child(ActorDestroy(self.other_actors[1], name="DestroyAdversary")) + sequence.add_child(ActorDestroy(self.other_actors[0], name="DestroyBlocker")) + sequence.add_child(DriveDistance(self.ego_vehicles[0], self._ego_end_distance, name="EndCondition")) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] def __del__(self): """ Remove all actors upon deletion """ self.remove_all_actors() + + # TODO: Pedestrian have an issue with large maps were setting them to dormant breaks them, + # so all functions below are meant to patch it until the fix is done + def _replace_walker(self, walker): + """As the adversary is probably, replace it with another one""" + type_id = walker.type_id + walker.destroy() + spawn_transform = self.ego_vehicles[0].get_transform() + spawn_transform.location.z -= 50 + walker = CarlaDataProvider.request_new_actor(type_id, spawn_transform) + if not walker: + raise ValueError("Couldn't spawn the walker substitute") + walker.set_simulate_physics(False) + walker.set_location(spawn_transform.location + carla.Location(z=-50)) + return walker + + def _setup_scenario_trigger(self, config): + """Normal scenario trigger but in parallel, a behavior that ensures the pedestrian stays active""" + trigger_tree = super()._setup_scenario_trigger(config) + + if not self.route_mode: + return trigger_tree + + parallel = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="ScenarioTrigger") + + parallel.add_child(MovePedestrianWithEgo(self.ego_vehicles[0], self.other_actors[1], 100)) + + parallel.add_child(trigger_tree) + return parallel diff --git a/srunner/scenarios/open_scenario.py b/srunner/scenarios/open_scenario.py index 2c04a8f70..c7f58d067 100644 --- a/srunner/scenarios/open_scenario.py +++ b/srunner/scenarios/open_scenario.py @@ -21,6 +21,7 @@ ChangeActorLaneOffset, ChangeActorWaypoints, ChangeLateralDistance from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ChangeActorControl, ChangeActorTargetSpeed from srunner.scenariomanager.timer import GameTime +from srunner.scenariomanager.weather_sim import OSCWeatherBehavior from srunner.scenarios.basic_scenario import BasicScenario from srunner.tools.openscenario_parser import OpenScenarioParser, oneshot_with_check, ParameterRef from srunner.tools.py_trees_port import Decorator @@ -522,6 +523,12 @@ def _create_behavior(self): return behavior + def _create_weather_behavior(self): + """ + Sets the osc weather behavior, which will monitor other behaviors, changing the weather + """ + return OSCWeatherBehavior() + def _create_condition_container(self, node, story, name='Conditions Group', sequence=None, maneuver=None, success_on_all=True): """ diff --git a/srunner/scenarios/opposite_vehicle_taking_priority.py b/srunner/scenarios/opposite_vehicle_taking_priority.py index e38ed6e0e..3bf945d61 100644 --- a/srunner/scenarios/opposite_vehicle_taking_priority.py +++ b/srunner/scenarios/opposite_vehicle_taking_priority.py @@ -19,7 +19,8 @@ from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorTransformSetter, ActorDestroy, TrafficLightFreezer, - BasicAgentBehavior) + ConstantVelocityAgentBehavior, + Idle) from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, InTimeToArrivalToLocation, @@ -31,14 +32,14 @@ filter_junction_wp_direction, get_closest_traffic_light) -from srunner.tools.background_manager import Scenario7Manager +from srunner.tools.background_manager import HandleJunctionScenario -class OppositeVehicleRunningRedLight(BasicScenario): + +class OppositeVehicleJunction(BasicScenario): """ - This class holds everything required for a scenario in which another vehicle runs a red light - in front of the ego, forcing it to react. This vehicles are 'special' ones such as police cars, - ambulances or firetrucks. + Scenario in which another vehicle enters the junction a tthe same time as the ego, + forcing it to break to avoid a collision """ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, @@ -50,66 +51,64 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals self._world = world self._map = CarlaDataProvider.get_map() self._source_dist = 30 - self._sink_dist = 20 - self._direction = None - self._opposite_bp_wildcards = ['*firetruck*', '*ambulance*', '*police*'] # Wildcard patterns of the blueprints + self._sink_dist = 10 + self._adversary_speed = 60 / 3.6 # m/s + + if 'direction' in config.other_parameters: + self._direction = config.other_parameters['direction']['value'] + else: + self._direction = "right" + + self.timeout = timeout - self._adversary_speed = 70 / 3.6 # Speed of the adversary [m/s] self._sync_time = 2.2 # Time the agent has to react to avoid the collision [s] - self._min_trigger_dist = 9.0 # Min distance to the collision location that triggers the adversary [m] - self._speed_duration_ratio = 2.0 - self._speed_distance_ratio = 1.5 + self._min_trigger_dist = 12.0 # Min distance to the collision location that triggers the adversary [m] - # Get the CDP seed or at routes, all copies of the scenario will have the same configuration - self._rng = CarlaDataProvider.get_random_seed() + self._lights = carla.VehicleLightState.Special1 | carla.VehicleLightState.Special2 - super(OppositeVehicleRunningRedLight, self).__init__("OppositeVehicleRunningRedLight", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) + super().__init__("OppositeVehicleJunction", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) def _initialize_actors(self, config): """ Custom initialization """ ego_location = config.trigger_points[0].location - ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) + self._ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) # Get the junction - starting_wp = ego_wp + starting_wp = self._ego_wp + ego_junction_dist = 0 while not starting_wp.is_junction: starting_wps = starting_wp.next(1.0) if len(starting_wps) == 0: raise ValueError("Failed to find junction as a waypoint with no next was detected") starting_wp = starting_wps[0] - junction = starting_wp.get_junction() + ego_junction_dist += 1 + self._junction = starting_wp.get_junction() # Get the opposite entry lane wp - possible_directions = ['right', 'left'] - self._rng.shuffle(possible_directions) - for direction in possible_directions: - entry_wps, _ = get_junction_topology(junction) - source_entry_wps = filter_junction_wp_direction(starting_wp, entry_wps, direction) - if source_entry_wps: - self._direction = direction - break - if not self._direction: - raise ValueError("Trying to find a lane to spawn the opposite actor but none was found") + entry_wps, _ = get_junction_topology(self._junction) + source_entry_wps = filter_junction_wp_direction(starting_wp, entry_wps, self._direction) + if not source_entry_wps: + raise ValueError("Couldn't find a lane for the given direction") # Get the source transform spawn_wp = source_entry_wps[0] - added_dist = 0 - while added_dist < self._source_dist: + source_junction_dist = 0 + while source_junction_dist < self._source_dist: spawn_wps = spawn_wp.previous(1.0) if len(spawn_wps) == 0: raise ValueError("Failed to find a source location as a waypoint with no previous was detected") if spawn_wps[0].is_junction: break spawn_wp = spawn_wps[0] - added_dist += 1 + source_junction_dist += 1 self._spawn_wp = spawn_wp source_transform = spawn_wp.transform @@ -117,14 +116,16 @@ def _initialize_actors(self, config): source_transform.location + carla.Location(z=0.1), source_transform.rotation ) + self.parking_slots.append(source_transform.location) # Spawn the actor and move it below ground - opposite_bp_wildcard = self._rng.choice(self._opposite_bp_wildcards) - opposite_actor = CarlaDataProvider.request_new_actor(opposite_bp_wildcard, self._spawn_location) + opposite_actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', self._spawn_location, attribute_filter={'special_type': 'emergency'}) if not opposite_actor: raise Exception("Couldn't spawn the actor") - opposite_actor.set_light_state(carla.VehicleLightState( - carla.VehicleLightState.Special1 | carla.VehicleLightState.Special2)) + lights = opposite_actor.get_light_state() + lights |= self._lights + opposite_actor.set_light_state(carla.VehicleLightState(lights)) self.other_actors.append(opposite_actor) opposite_transform = carla.Transform( @@ -143,17 +144,57 @@ def _initialize_actors(self, config): # get the collision location self._collision_location = get_geometric_linear_intersection( - starting_wp.transform.location, source_entry_wps[0].transform.location) + starting_wp.transform.location, source_entry_wps[0].transform.location, True) if not self._collision_location: raise ValueError("Couldn't find an intersection point") - # Get the relevant traffic lights - tls = self._world.get_traffic_lights_in_junction(junction.id) - ego_tl = get_closest_traffic_light(ego_wp, tls) - source_tl = get_closest_traffic_light(self._spawn_wp, tls,) + # Get the z component + collision_wp = self._map.get_waypoint(self._collision_location) + self._collision_location.z = collision_wp.transform.location.z + + def _create_behavior(self): + raise NotImplementedError("Found missing behavior") + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class OppositeVehicleRunningRedLight(OppositeVehicleJunction): + """ + Signalized junction version, where the other vehicle runs a red light + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + super()._initialize_actors(config) + + tls = self._world.get_traffic_lights_in_junction(self._junction.id) + ego_tl = get_closest_traffic_light(self._ego_wp, tls) self._tl_dict = {} for tl in tls: - if tl in (ego_tl, source_tl): + if tl == ego_tl: self._tl_dict[tl] = carla.TrafficLightState.Green else: self._tl_dict[tl] = carla.TrafficLightState.Red @@ -163,7 +204,7 @@ def _create_behavior(self): Hero vehicle is entering a junction in an urban area, at a signalized intersection, while another actor runs a red lift, forcing the ego to break. """ - sequence = py_trees.composites.Sequence() + sequence = py_trees.composites.Sequence(name="OppositeVehicleRunningRedLight") # Wait until ego is close to the adversary trigger_adversary = py_trees.composites.Parallel( @@ -174,33 +215,103 @@ def _create_behavior(self): self.ego_vehicles[0], self._collision_location, self._min_trigger_dist)) sequence.add_child(trigger_adversary) - sequence.add_child(BasicAgentBehavior( - self.other_actors[0], target_location=self._sink_wp.transform.location, - target_speed=self._adversary_speed, opt_dict={'ignore_vehicles': True}, name="AdversaryCrossing")) + + end_location = self._sink_wp.transform.location + start_location = self._spawn_wp.transform.location + time = start_location.distance(end_location) / self._adversary_speed main_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) - main_behavior.add_child(TrafficLightFreezer(self._tl_dict)) - main_behavior.add_child(sequence) + main_behavior.add_child(ConstantVelocityAgentBehavior( + self.other_actors[0], target_location=end_location, + target_speed=self._adversary_speed, + opt_dict={'ignore_vehicles': True, 'ignore_traffic_lights': True}, + name="AdversaryCrossing") + ) + main_behavior.add_child(Idle(time)) + + sequence.add_child(main_behavior) + sequence.add_child(ActorDestroy(self.other_actors[0])) + sequence.add_child(WaitEndIntersection(self.ego_vehicles[0])) + + tls_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + tls_behavior.add_child(TrafficLightFreezer(self._tl_dict)) + tls_behavior.add_child(sequence) root = py_trees.composites.Sequence() - if CarlaDataProvider.get_ego_vehicle_route(): - root.add_child(Scenario7Manager(self._direction)) + if self.route_mode: + root.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=[self._spawn_wp], + remove_exits=[self._sink_wp], + stop_entries=False, + extend_road_exit=0 + )) root.add_child(ActorTransformSetter(self.other_actors[0], self._spawn_location)) - root.add_child(main_behavior) - root.add_child(ActorDestroy(self.other_actors[0])) - root.add_child(WaitEndIntersection(self.ego_vehicles[0])) + root.add_child(tls_behavior) return root - def _create_test_criteria(self): + +class OppositeVehicleTakingPriority(OppositeVehicleJunction): + """ + Non signalized version + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): """ - A list of all test criteria will be created that is later used - in parallel behavior tree. + Setup all relevant parameters and create scenario + and instantiate scenario manager """ - return [CollisionTest(self.ego_vehicles[0])] + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) - def __del__(self): + def _create_behavior(self): """ - Remove all actors and traffic lights upon deletion + Hero vehicle is entering a junction in an urban area, at a signalized intersection, + while another actor runs a red lift, forcing the ego to break. """ - self.remove_all_actors() + sequence = py_trees.composites.Sequence(name="OppositeVehicleTakingPriority") + + # Wait until ego is close to the adversary + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._sync_time, self._collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._collision_location, self._min_trigger_dist)) + + sequence.add_child(trigger_adversary) + + end_location = self._sink_wp.transform.location + start_location = self._spawn_wp.transform.location + time = start_location.distance(end_location) / self._adversary_speed + + main_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + main_behavior.add_child(ConstantVelocityAgentBehavior( + self.other_actors[0], target_location=end_location, + target_speed=self._adversary_speed, + opt_dict={'ignore_vehicles': True, 'ignore_traffic_lights': True}, + name="AdversaryCrossing") + ) + main_behavior.add_child(Idle(time)) + + sequence.add_child(main_behavior) + + root = py_trees.composites.Sequence() + if self.route_mode: + root.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=[self._spawn_wp], + remove_exits=[self._sink_wp], + stop_entries=True, + extend_road_exit=0 + )) + + root.add_child(ActorTransformSetter(self.other_actors[0], self._spawn_location)) + root.add_child(sequence) + root.add_child(ActorDestroy(self.other_actors[0])) + root.add_child(WaitEndIntersection(self.ego_vehicles[0])) + + return root diff --git a/srunner/scenarios/parking_cut_in.py b/srunner/scenarios/parking_cut_in.py new file mode 100644 index 000000000..30c8fee63 --- /dev/null +++ b/srunner/scenarios/parking_cut_in.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Parking cut in scenario synchronizes a vehicle that is parked at a side lane +to cut in in front of the ego vehicle, forcing it to break +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + BasicAgentBehavior) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, + InTimeToArrivalToLocation, + DriveDistance) +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import LeaveSpaceInFront, ChangeRoadBehavior + + +class ParkingCutIn(BasicScenario): + + """ + Parking cut in scenario synchronizes a vehicle that is parked at a side lane + to cut in in front of the ego vehicle, forcing it to break + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=60): + """ + Setup all relevant parameters and create scenario + """ + self._wmap = CarlaDataProvider.get_map() + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) + + self._cut_in_distance = 35 + self._blocker_distance = 28 + + self._adversary_speed = 13.0 # Speed of the adversary [m/s] + self._reaction_time = 2.35 # Time the agent has to react to avoid the collision [s] + self._min_trigger_dist = 10.0 # Min distance to the collision location that triggers the adversary [m] + self._end_distance = 30 + self._extra_space = 20 + + self._bp_attributes = {'base_type': 'car', 'generation': 2, 'special_type': ''} + + self.timeout = timeout + + if 'direction' in config.other_parameters: + self._direction = config.other_parameters['direction']['value'] + else: + self._direction = "right" + + super().__init__("ParkingCutIn", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + # Spawn the blocker vehicle + blocker_wps = self._reference_waypoint.next(self._blocker_distance) + if not blocker_wps: + raise ValueError("Couldn't find a proper position for the cut in vehicle") + self._blocker_wp = blocker_wps[0] + + if self._direction == 'left': + parking_wp = self._blocker_wp.get_left_lane() + else: + parking_wp = self._blocker_wp.get_right_lane() + + self.parking_slots.append(parking_wp.transform.location) + + self._blocker_actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', parking_wp.transform, 'scenario no lights', attribute_filter={'base_type': 'car', 'generation': 2}) + if not self._blocker_actor: + raise ValueError("Couldn't spawn the parked actor") + self._blocker_actor.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(self._blocker_actor) + + side_location = self._get_displaced_location(self._blocker_actor, parking_wp) + self._blocker_actor.set_location(side_location) + + collision_wps = self._reference_waypoint.next(self._cut_in_distance) + if not collision_wps: + raise ValueError("Couldn't find a proper position for the cut in vehicle") + self._collision_wp = collision_wps[0] + + # Get the parking direction + if self._direction == 'left': + parking_wp = self._collision_wp.get_left_lane() + else: + parking_wp = self._collision_wp.get_right_lane() + + self.parking_slots.append(parking_wp.transform.location) + + self._parked_actor = CarlaDataProvider.request_new_actor( + 'vehicle.*', parking_wp.transform, 'scenario', attribute_filter=self._bp_attributes) + if not self._parked_actor: + raise ValueError("Couldn't spawn the parked actor") + self.other_actors.append(self._parked_actor) + + side_location = self._get_displaced_location(self._parked_actor, parking_wp) + self._parked_actor.set_location(side_location) + + def _get_displaced_location(self, actor, wp): + """ + Calculates the location such that the actor is at the sidemost part of the lane + """ + # Move the actor to the edge of the lane near the sidewalk + displacement = (wp.lane_width - actor.bounding_box.extent.y) / 4 + displacement_vector = wp.transform.get_right_vector() + if self._direction == 'left': + displacement_vector *= -1 + + new_location = wp.transform.location + carla.Location(x=displacement*displacement_vector.x, + y=displacement*displacement_vector.y, + z=displacement*displacement_vector.z) + new_location.z += 0.05 # Just in case, avoid collisions with the ground + return new_location + + def _create_behavior(self): + """ + After invoking this scenario, a parked vehicle will wait for the ego to + be close-by, merging into its lane, forcing it to break. + """ + sequence = py_trees.composites.Sequence(name="ParkingCutIn") + if self.route_mode: + sequence.add_child(LeaveSpaceInFront(self._cut_in_distance + 10)) + + collision_location = self._collision_wp.transform.location + + # Wait until ego is close to the adversary + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._reaction_time, collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + sequence.add_child(trigger_adversary) + + if self.route_mode: + sequence.add_child(ChangeRoadBehavior(extra_space=self._extra_space)) + + # Move the adversary + cut_in = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="Cut in behavior") + cut_in.add_child(BasicAgentBehavior(self.other_actors[1], opt_dict={'ignore_traffic_lights': True})) + cut_in.add_child(DriveDistance(self.other_actors[1], self._end_distance)) + sequence.add_child(cut_in) + + # Remove everything + sequence.add_child(ActorDestroy(self.other_actors[0], name="DestroyAdversary")) + sequence.add_child(ActorDestroy(self.other_actors[1], name="DestroyBlocker")) + + if self.route_mode: + sequence.add_child(ChangeRoadBehavior(extra_space=0)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/parking_exit.py b/srunner/scenarios/parking_exit.py new file mode 100644 index 000000000..acbb69643 --- /dev/null +++ b/srunner/scenarios/parking_exit.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenario in which the ego is parked between two vehicles and has to maneuver to start the route. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + ActorTransformSetter, + WaitForever, + ChangeAutoPilot, + ScenarioTimeout) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import DriveDistance +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import ChangeRoadBehavior + + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + + +class ParkingExit(BasicScenario): + """ + This class holds everything required for a scenario in which the ego would be teleported to the parking lane. + Once the scenario is triggered, the OutsideRouteLanesTest will be deactivated since the ego is out of the driving lane. + Then blocking vehicles will be generated in front of and behind the parking point. + The ego need to exit from the parking lane and then merge into the driving lane. + After the ego is {end_distance} meters away from the parking point, the OutsideRouteLanesTest will be activated and the scenario ends. + + Note 1: For route mode, this shall be the first scenario of the route. The trigger point shall be the first point of the route waypoints. + + Note 2: Make sure there are enough space for spawning blocking vehicles. + """ + + def __init__(self, world, ego_vehicles, config, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self._tm = CarlaDataProvider.get_client().get_trafficmanager( + CarlaDataProvider.get_traffic_manager_port()) + self.timeout = timeout + + self._bp_attributes = {'base_type': 'car', 'generation': 2} + self._side_end_distance = 50 + + self._front_vehicle_distance = get_value_parameter(config, 'front_vehicle_distance', float, 20) + self._behind_vehicle_distance = get_value_parameter(config, 'behind_vehicle_distance', float, 10) + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + self._flow_distance = get_value_parameter(config, 'flow_distance', float, 25) + self._max_speed = get_value_parameter(config, 'speed', float, 60) + self._scenario_timeout = 240 + + self._end_distance = self._front_vehicle_distance + 15 + + # Get parking_waypoint based on trigger_point + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._map.get_waypoint(self._trigger_location) + if self._direction == "left": + self._parking_waypoint = self._reference_waypoint.get_left_lane() + else: + self._parking_waypoint = self._reference_waypoint.get_right_lane() + + if self._parking_waypoint is None: + raise Exception( + "Couldn't find parking point on the {} side".format(self._direction)) + + super().__init__("ParkingExit", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + + # Spawn the actor in front of the ego + front_points = self._parking_waypoint.next( + self._front_vehicle_distance) + if not front_points: + raise ValueError("Couldn't find viable position for the vehicle in front of the parking point") + + self.parking_slots.append(front_points[0].transform.location) + + actor_front = CarlaDataProvider.request_new_actor( + 'vehicle.*', front_points[0].transform, rolename='scenario no lights', attribute_filter=self._bp_attributes) + if actor_front is None: + raise ValueError("Couldn't spawn the vehicle in front of the parking point") + actor_front.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(actor_front) + + # And move it to the side + side_location = self._get_displaced_location(actor_front, front_points[0]) + actor_front.set_location(side_location) + + # Spawn the actor behind the ego + behind_points = self._parking_waypoint.previous( + self._behind_vehicle_distance) + if not behind_points: + raise ValueError("Couldn't find viable position for the vehicle behind the parking point") + + self.parking_slots.append(behind_points[0].transform.location) + + actor_behind = CarlaDataProvider.request_new_actor( + 'vehicle.*', behind_points[0].transform, rolename='scenario no lights', attribute_filter=self._bp_attributes) + if actor_behind is None: + actor_front.destroy() + raise ValueError("Couldn't spawn the vehicle behind the parking point") + actor_behind.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(actor_behind) + + # And move it to the side + side_location = self._get_displaced_location(actor_behind, behind_points[0]) + actor_behind.set_location(side_location) + + # Move the ego to its side position + self._ego_location = self._get_displaced_location(self.ego_vehicles[0], self._parking_waypoint) + self.parking_slots.append(self._ego_location) + self.ego_vehicles[0].set_location(self._ego_location) + + # Spawn the actor at the side of the ego + actor_side = CarlaDataProvider.request_new_actor( + 'vehicle.*', self._reference_waypoint.transform, attribute_filter=self._bp_attributes) + if actor_side is None: + raise ValueError("Couldn't spawn the vehicle at the side of the parking point") + self.other_actors.append(actor_side) + self._tm.update_vehicle_lights(actor_side, True) + + self._end_side_transform = self.ego_vehicles[0].get_transform() + self._end_side_transform.location.z -= 500 + + def _get_displaced_location(self, actor, wp): + """ + Calculates the transforming such that the actor is at the sidemost part of the lane + """ + # Move the actor to the edge of the lane near the sidewalk + displacement = (wp.lane_width - actor.bounding_box.extent.y) / 4 + displacement_vector = wp.transform.get_right_vector() + if self._direction == 'left': + displacement_vector *= -1 + + new_location = wp.transform.location + carla.Location(x=displacement*displacement_vector.x, + y=displacement*displacement_vector.y, + z=displacement*displacement_vector.z) + new_location.z += 0.05 # Just in case, avoid collisions with the ground + return new_location + + def _create_behavior(self): + """ + Deactivate OutsideRouteLanesTest, then move ego to the parking point, + generate blocking vehicles in front of and behind the ego. + After ego drives away, activate OutsideRouteLanesTest, end scenario. + """ + + sequence = py_trees.composites.Sequence(name="ParkingExit") + sequence.add_child(ChangeRoadBehavior(spawn_dist=self._flow_distance)) + root = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + + side_actor_behavior = py_trees.composites.Sequence() + side_actor_behavior.add_child(ChangeAutoPilot(self.other_actors[2], True)) + side_actor_behavior.add_child(DriveDistance(self.other_actors[2], self._side_end_distance)) + side_actor_behavior.add_child(ActorTransformSetter(self.other_actors[2], self._end_side_transform, False)) + side_actor_behavior.add_child(WaitForever()) + root.add_child(side_actor_behavior) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + root.add_child(end_condition) + + sequence.add_child(root) + + for actor in self.other_actors: + sequence.add_child(ActorDestroy(actor)) + sequence.add_child(ChangeRoadBehavior(spawn_dist=15)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/pedestrian_crossing.py b/srunner/scenarios/pedestrian_crossing.py new file mode 100644 index 000000000..6cb70c2de --- /dev/null +++ b/srunner/scenarios/pedestrian_crossing.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Pedestrians crossing through the middle of the lane. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + KeepVelocity, + WaitForever, + Idle, + ActorTransformSetter, + MovePedestrianWithEgo) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, + InTimeToArrivalToLocation, + DriveDistance) +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import HandleJunctionScenario + + +def convert_dict_to_location(actor_dict): + """ + Convert a JSON string to a Carla.Location + """ + location = carla.Location( + x=float(actor_dict['x']), + y=float(actor_dict['y']), + z=float(actor_dict['z']) + ) + return location + + +class PedestrianCrossing(BasicScenario): + + """ + This class holds everything required for a group of natual pedestrians crossing the road. + The ego vehicle is passing through a road, + And encounters a group of pedestrians crossing the road. + + This is a single ego vehicle scenario. + + Notice that the initial pedestrian will walk from the start of the junction ahead to end_walker_flow_1. + """ + + def __init__(self, world, ego_vehicles, config, debug_mode=False, criteria_enable=True, timeout=60): + """ + Setup all relevant parameters and create scenario + """ + self._wmap = CarlaDataProvider.get_map() + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._wmap.get_waypoint(self._trigger_location) + self._rng = CarlaDataProvider.get_random_seed() + + self._adversary_speed = 1.3 # Speed of the adversary [m/s] + self._reaction_time = 3.5 # Time the agent has to react to avoid the collision [s] + self._min_trigger_dist = 12.0 # Min distance to the collision location that triggers the adversary [m] + self._ego_end_distance = 40 + self.timeout = timeout + + self._walker_data = [ + {'x': 0.4, 'y': 1.5, 'z': 1.2, 'yaw': 270}, + {'x': 1, 'y': 2.5, 'z': 1.2, 'yaw': 270}, + {'x': 1.6, 'y': 0.5, 'z': 1.2, 'yaw': 270} + ] + + for walker_data in self._walker_data: + walker_data['idle_time'] = self._rng.uniform(0, 1.5) + walker_data['speed'] = self._rng.uniform(1.3, 2.0) + + super().__init__("PedestrianCrossing", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _get_walker_transform(self, wp, displacement): + disp_x = displacement['x'] + disp_y = displacement['y'] + disp_z = displacement['z'] + disp_yaw = displacement['yaw'] + + # Displace it to the crosswalk. Move forwards towards the crosswalk + start_vec = wp.transform.get_forward_vector() + start_right_vec = wp.transform.get_right_vector() + + spawn_loc = wp.transform.location + carla.Location( + disp_x * start_vec.x + disp_y * start_right_vec.x, + disp_x * start_vec.y + disp_y * start_right_vec.y, + disp_x * start_vec.z + disp_y * start_right_vec.z + disp_z + ) + + spawn_rotation = wp.transform.rotation + spawn_rotation.yaw += disp_yaw + return carla.Transform(spawn_loc, spawn_rotation) + + def _initialize_actors(self, config): + + # Get the start point of the initial pedestrian + collision_wp = self._reference_waypoint + while True: + next_wps = collision_wp.next(1) + if not next_wps: + raise ValueError("Couldn't find a waypoint to spawn the pedestrians") + if next_wps[0].is_junction: + break + collision_wp = next_wps[0] + + self._collision_wp = collision_wp + + # Get the crosswalk start point + start_wp = collision_wp + while start_wp.lane_type != carla.LaneType.Sidewalk: + wp = start_wp.get_right_lane() + if wp is None: + raise ValueError("Couldn't find a waypoint to start the flow") + start_wp = wp + + # Spawn the walkers + for i, walker_data in enumerate(self._walker_data): + spawn_transform = self._get_walker_transform(start_wp, walker_data) + walker = CarlaDataProvider.request_new_actor('walker.*', spawn_transform) + if walker is None: + for walker in self.other_actors: + walker.destroy() + raise ValueError("Failed to spawn an adversary") + + walker.set_location(spawn_transform.location + carla.Location(z=-200)) + walker = self._replace_walker(walker) + + self.other_actors.append(walker) + + collision_dist = spawn_transform.location.distance(self._collision_wp.transform.location) + + # Distance and duration to cross the whole road + a bit more (supposing symetry in both directions) + move_dist = 2.3 * collision_dist + walker_data['transform'] = spawn_transform + walker_data['distance'] = move_dist + walker_data['duration'] = move_dist / walker_data['speed'] + + def _create_behavior(self): + """ + After invoking this scenario, cyclist will wait for the user + controlled vehicle to enter trigger distance region, + the cyclist starts crossing the road once the condition meets, + then after 60 seconds, a timeout stops the scenario + """ + sequence = py_trees.composites.Sequence(name="PedestrianCrossing") + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=False, + clear_ego_entry=True, + remove_entries=[], + remove_exits=[], + stop_entries=False, + extend_road_exit=0 + )) + + for walker_actor, walker_data in zip(self.other_actors, self._walker_data): + sequence.add_child(ActorTransformSetter(walker_actor, walker_data['transform'], True)) + + collision_location = self._collision_wp.transform.location + + # Wait until ego is close to the adversary + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerAdversaryStart") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._reaction_time, collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + sequence.add_child(trigger_adversary) + + # Move the walkers + main_behavior = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="WalkerMovement") + + for walker_actor, walker_data in zip(self.other_actors, self._walker_data): + walker_sequence = py_trees.composites.Sequence(name="WalkerCrossing") + walker_sequence.add_child(Idle(walker_data['idle_time'])) + walker_sequence.add_child(KeepVelocity( + walker_actor, walker_data['speed'], False, walker_data['duration'], walker_data['distance'])) + walker_sequence.add_child(ActorDestroy(walker_actor, name="DestroyAdversary")) + walker_sequence.add_child(WaitForever()) + + main_behavior.add_child(walker_sequence) + + main_behavior.add_child(DriveDistance(self.ego_vehicles[0], self._ego_end_distance, name="EndCondition")) + sequence.add_child(main_behavior) + + # Remove everything + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + if self.route_mode: + return [] + return [CollisionTest(self.ego_vehicles[0])] + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() + + # TODO: Pedestrian have an issue with large maps were setting them to dormant breaks them, + # so all functions below are meant to patch it until the fix is done + def _replace_walker(self, walker): + """As the adversary is probably, replace it with another one""" + type_id = walker.type_id + walker.destroy() + spawn_transform = self.ego_vehicles[0].get_transform() + spawn_transform.location.z -= 50 + walker = CarlaDataProvider.request_new_actor(type_id, spawn_transform) + if not walker: + raise ValueError("Couldn't spawn the walker substitute") + walker.set_simulate_physics(False) + walker.set_location(spawn_transform.location + carla.Location(z=-50)) + return walker + + def _setup_scenario_trigger(self, config): + """Normal scenario trigger but in parallel, a behavior that ensures the pedestrian stays active""" + trigger_tree = super()._setup_scenario_trigger(config) + + if not self.route_mode: + return trigger_tree + + parallel = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="ScenarioTrigger") + + for i, walker in enumerate(reversed(self.other_actors)): + parallel.add_child(MovePedestrianWithEgo(self.ego_vehicles[0], walker, 100)) + + parallel.add_child(trigger_tree) + return parallel diff --git a/srunner/scenarios/route_obstacles.py b/srunner/scenarios/route_obstacles.py new file mode 100644 index 000000000..8a0a3dc11 --- /dev/null +++ b/srunner/scenarios/route_obstacles.py @@ -0,0 +1,719 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenarios in which another (opposite) vehicle 'illegally' takes +priority, e.g. by running a red traffic light. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + SwitchWrongDirectionTest, + BasicAgentBehavior, + ScenarioTimeout, + Idle, WaitForever, + HandBrakeVehicle, + OppositeActorFlow) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (DriveDistance, + InTriggerDistanceToLocation, + InTriggerDistanceToVehicle, + WaitUntilInFront, + WaitUntilInFrontPosition) +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import LeaveSpaceInFront, SetMaxSpeed, ChangeOppositeBehavior, ChangeRoadBehavior + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default + + +class Accident(BasicScenario): + """ + This class holds everything required for a scenario in which there is an accident + in front of the ego, forcing it to lane change. A police vehicle is located before + two other cars that have been in an accident. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._first_distance = 10 + self._second_distance = 6 + + self._trigger_distance = 50 + self._end_distance = 50 + self._wait_duration = 5 + self._offset = 0.6 + + self._lights = carla.VehicleLightState.Special1 | carla.VehicleLightState.Special2 | carla.VehicleLightState.Position + + self._distance = get_value_parameter(config, 'distance', float, 120) + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + self._max_speed = get_value_parameter(config, 'speed', float, 60) + self._scenario_timeout = 240 + + super().__init__( + "Accident", ego_vehicles, config, world, randomize, debug_mode, criteria_enable=criteria_enable) + + def _move_waypoint_forward(self, wp, distance): + dist = 0 + next_wp = wp + while dist < distance: + next_wps = next_wp.next(1) + if not next_wps or next_wps[0].is_junction: + break + next_wp = next_wps[0] + dist += 1 + return next_wp + + def _spawn_side_prop(self, wp): + # Spawn the accident indication signal + prop_wp = wp + while True: + if self._direction == "right": + wp = prop_wp.get_right_lane() + else: + wp = prop_wp.get_left_lane() + if wp is None or wp.lane_type not in (carla.LaneType.Driving, carla.LaneType.Parking): + break + prop_wp = wp + + displacement = 0.3 * prop_wp.lane_width + r_vec = prop_wp.transform.get_right_vector() + if self._direction == 'left': + r_vec *= -1 + + spawn_transform = wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=0.2) + spawn_transform.rotation.yaw += 90 + signal_prop = CarlaDataProvider.request_new_actor('static.prop.warningaccident', spawn_transform) + if not signal_prop: + raise ValueError("Couldn't spawn the indication prop asset") + signal_prop.set_simulate_physics(False) + self.other_actors.append(signal_prop) + + def _spawn_obstacle(self, wp, blueprint, accident_actor=False): + """ + Spawns the obstacle actor by displacing its position to the right + """ + displacement = self._offset * wp.lane_width / 2 + r_vec = wp.transform.get_right_vector() + if self._direction == 'left': + r_vec *= -1 + + spawn_transform = wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=1) + if accident_actor: + actor = CarlaDataProvider.request_new_actor( + blueprint, spawn_transform, rolename='scenario no lights', attribute_filter={'base_type': 'car', 'generation': 2}) + else: + actor = CarlaDataProvider.request_new_actor( + blueprint, spawn_transform, rolename='scenario') + if not actor: + raise ValueError("Couldn't spawn an obstacle actor") + + return actor + + def _initialize_actors(self, config): + """ + Custom initialization + """ + starting_wp = self._map.get_waypoint(config.trigger_points[0].location) + + # Spawn the accident indication signal + self._spawn_side_prop(starting_wp) + + # Spawn the police vehicle + self._accident_wp = self._move_waypoint_forward(starting_wp, self._distance) + police_car = self._spawn_obstacle(self._accident_wp, 'vehicle.dodge.charger_police_2020') + + # Set its initial conditions + lights = police_car.get_light_state() + lights |= self._lights + police_car.set_light_state(carla.VehicleLightState(lights)) + police_car.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(police_car) + + # Create the first vehicle that has been in the accident + self._first_vehicle_wp = self._move_waypoint_forward(self._accident_wp, self._first_distance) + first_actor = self._spawn_obstacle(self._first_vehicle_wp, 'vehicle.*', True) + + # Set its initial conditions + first_actor.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(first_actor) + + # Create the second vehicle that has been in the accident + second_vehicle_wp = self._move_waypoint_forward(self._first_vehicle_wp, self._second_distance) + second_actor = self._spawn_obstacle(second_vehicle_wp, 'vehicle.*', True) + + self._accident_wp = second_vehicle_wp + self._end_wp = self._move_waypoint_forward(second_vehicle_wp, self._end_distance) + + # Set its initial conditions + second_actor.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(second_actor) + + def _create_behavior(self): + """ + The vehicle has to drive the reach a specific point but an accident is in the middle of the road, + blocking its route and forcing it to lane change. + """ + root = py_trees.composites.Sequence(name="Accident") + if self.route_mode: + total_dist = self._distance + self._first_distance + self._second_distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) + + behavior = py_trees.composites.Sequence() + behavior.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._first_vehicle_wp.transform.location, self._trigger_distance)) + behavior.add_child(Idle(self._wait_duration)) + if self.route_mode: + behavior.add_child(SetMaxSpeed(self._max_speed)) + behavior.add_child(WaitForever()) + + end_condition.add_child(behavior) + root.add_child(end_condition) + + if self.route_mode: + root.add_child(SetMaxSpeed(0)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class AccidentTwoWays(Accident): + """ + Variation of the Accident scenario but the ego now has to invade the opposite lane + """ + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=180): + + self._opposite_interval = get_interval_parameter(config, 'frequency', float, [20, 100]) + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _create_behavior(self): + """ + The vehicle has to drive the whole predetermined distance. Adapt the opposite flow to + let the ego invade the opposite lane. + """ + reference_wp = self._accident_wp.get_left_lane() + if not reference_wp: + raise ValueError("Couldnt find a left lane to spawn the opposite traffic") + + root = py_trees.composites.Sequence(name="AccidentTwoWays") + if self.route_mode: + total_dist = self._distance + self._first_distance + self._second_distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) + + behavior = py_trees.composites.Sequence() + behavior.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._first_vehicle_wp.transform.location, self._trigger_distance)) + behavior.add_child(Idle(self._wait_duration)) + if self.route_mode: + behavior.add_child(SwitchWrongDirectionTest(False)) + behavior.add_child(ChangeOppositeBehavior(active=False)) + behavior.add_child(OppositeActorFlow(reference_wp, self.ego_vehicles[0], self._opposite_interval)) + + end_condition.add_child(behavior) + root.add_child(end_condition) + + if self.route_mode: + root.add_child(SwitchWrongDirectionTest(True)) + root.add_child(ChangeOppositeBehavior(active=True)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root + +class ParkedObstacle(BasicScenario): + """ + Scenarios in which a parked vehicle is incorrectly parked, + forcing the ego to lane change out of the route's lane + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._trigger_distance = 50 + self._end_distance = 50 + self._wait_duration = 5 + self._offset = 0.7 + + self._lights = carla.VehicleLightState.RightBlinker | carla.VehicleLightState.LeftBlinker | carla.VehicleLightState.Position + + self._distance = get_value_parameter(config, 'distance', float, 120) + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + self._max_speed = get_value_parameter(config, 'speed', float, 60) + self._scenario_timeout = 240 + + super().__init__( + "ParkedObstacle", ego_vehicles, config, world, randomize, debug_mode, criteria_enable=criteria_enable) + + def _move_waypoint_forward(self, wp, distance): + dist = 0 + next_wp = wp + while dist < distance: + next_wps = next_wp.next(1) + if not next_wps or next_wps[0].is_junction: + break + next_wp = next_wps[0] + dist += 1 + return next_wp + + def _spawn_side_prop(self, wp): + # Spawn the accident indication signal + prop_wp = wp + while True: + if self._direction == "right": + wp = prop_wp.get_right_lane() + else: + wp = prop_wp.get_left_lane() + if wp is None or wp.lane_type not in (carla.LaneType.Driving, carla.LaneType.Parking): + break + prop_wp = wp + + displacement = 0.3 * prop_wp.lane_width + r_vec = prop_wp.transform.get_right_vector() + if self._direction == 'left': + r_vec *= -1 + + spawn_transform = wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=0.2) + spawn_transform.rotation.yaw += 90 + signal_prop = CarlaDataProvider.request_new_actor('static.prop.warningaccident', spawn_transform) + if not signal_prop: + raise ValueError("Couldn't spawn the indication prop asset") + signal_prop.set_simulate_physics(False) + self.other_actors.append(signal_prop) + + def _spawn_obstacle(self, wp, blueprint): + """ + Spawns the obstacle actor by displacing its position to the right + """ + displacement = self._offset * wp.lane_width / 2 + r_vec = wp.transform.get_right_vector() + if self._direction == 'left': + r_vec *= -1 + + spawn_transform = wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=1) + actor = CarlaDataProvider.request_new_actor( + blueprint, spawn_transform, rolename='scenario no lights', attribute_filter={'base_type': 'car', 'generation': 2}) + if not actor: + raise ValueError("Couldn't spawn an obstacle actor") + + return actor + + def _initialize_actors(self, config): + """ + Custom initialization + """ + self._starting_wp = self._map.get_waypoint(config.trigger_points[0].location) + + # Create the side prop + self._spawn_side_prop(self._starting_wp) + + # Create the first vehicle that has been in the accident + self._vehicle_wp = self._move_waypoint_forward(self._starting_wp, self._distance) + parked_actor = self._spawn_obstacle(self._vehicle_wp, 'vehicle.*') + + lights = parked_actor.get_light_state() + lights |= self._lights + parked_actor.set_light_state(carla.VehicleLightState(lights)) + parked_actor.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(parked_actor) + + self._end_wp = self._move_waypoint_forward(self._vehicle_wp, self._end_distance) + + def _create_behavior(self): + """ + The vehicle has to drive the whole predetermined distance. + """ + root = py_trees.composites.Sequence(name="ParkedObstacle") + if self.route_mode: + total_dist = self._distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) + + behavior = py_trees.composites.Sequence() + behavior.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._vehicle_wp.transform.location, self._trigger_distance)) + behavior.add_child(Idle(self._wait_duration)) + if self.route_mode: + behavior.add_child(SetMaxSpeed(self._max_speed)) + behavior.add_child(WaitForever()) + + end_condition.add_child(behavior) + root.add_child(end_condition) + + if self.route_mode: + root.add_child(SetMaxSpeed(0)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class ParkedObstacleTwoWays(ParkedObstacle): + """ + Variation of the ParkedObstacle scenario but the ego now has to invade the opposite lane + """ + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=180): + + self._opposite_interval = get_interval_parameter(config, 'frequency', float, [20, 100]) + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _create_behavior(self): + """ + The vehicle has to drive the whole predetermined distance. Adapt the opposite flow to + let the ego invade the opposite lane. + """ + reference_wp = self._vehicle_wp.get_left_lane() + if not reference_wp: + raise ValueError("Couldnt find a left lane to spawn the opposite traffic") + + root = py_trees.composites.Sequence(name="ParkedObstacleTwoWays") + if self.route_mode: + total_dist = self._distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) + + behavior = py_trees.composites.Sequence() + behavior.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], self._vehicle_wp.transform.location, self._trigger_distance)) + behavior.add_child(Idle(self._wait_duration)) + if self.route_mode: + behavior.add_child(SwitchWrongDirectionTest(False)) + behavior.add_child(ChangeOppositeBehavior(active=False)) + behavior.add_child(OppositeActorFlow(reference_wp, self.ego_vehicles[0], self._opposite_interval)) + + end_condition.add_child(behavior) + root.add_child(end_condition) + + if self.route_mode: + root.add_child(SwitchWrongDirectionTest(True)) + root.add_child(ChangeOppositeBehavior(active=True)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root + + +class HazardAtSideLane(BasicScenario): + """ + Added the dangerous scene of ego vehicles driving on roads without sidewalks, + with three bicycles encroaching on some roads in front. + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + + self._obstacle_distance = 9 + self._trigger_distance = 50 + self._end_distance = 50 + self._extra_space = 30 + + self._offset = 0.55 + self._wait_duration = 5 + + self._target_locs = [] + + self._bicycle_bps = ["vehicle.bh.crossbike", "vehicle.diamondback.century", "vehicle.gazelle.omafiets"] + + self._distance = get_value_parameter(config, 'distance', float, 100) + self._max_speed = get_value_parameter(config, 'speed', float, 60) + self._bicycle_speed = get_value_parameter(config, 'bicycle_speed', float, 10) + self._bicycle_drive_distance = get_value_parameter(config, 'bicycle_drive_distance', float, 50) + self._scenario_timeout = 240 + + super().__init__("HazardAtSideLane", + ego_vehicles, + config, + world, + randomize, + debug_mode, + criteria_enable=criteria_enable) + + def _move_waypoint_forward(self, wp, distance): + dist = 0 + next_wp = wp + while dist < distance: + next_wps = next_wp.next(1) + if not next_wps or next_wps[0].is_junction: + break + next_wp = next_wps[0] + dist += 1 + return next_wp + + def _spawn_obstacle(self, wp, blueprint): + """ + Spawns the obstacle actor by displacing its position to the right + """ + displacement = self._offset * wp.lane_width / 2 + r_vec = wp.transform.get_right_vector() + + spawn_transform = wp.transform + spawn_transform.location += carla.Location(x=displacement * r_vec.x, y=displacement * r_vec.y, z=1) + actor = CarlaDataProvider.request_new_actor(blueprint, spawn_transform) + if not actor: + raise ValueError("Couldn't spawn an obstacle actor") + + return actor + + def _initialize_actors(self, config): + """ + Custom initialization + """ + rng = CarlaDataProvider.get_random_seed() + self._starting_wp = self._map.get_waypoint(config.trigger_points[0].location) + + # Spawn the first bicycle + first_wp = self._move_waypoint_forward(self._starting_wp, self._distance) + bicycle_1 = self._spawn_obstacle(first_wp, rng.choice(self._bicycle_bps)) + + wps = first_wp.next(self._bicycle_drive_distance) + if not wps: + raise ValueError("Couldn't find an end location for the bicycles") + self._target_locs.append(wps[0].transform.location) + + # Set its initial conditions + bicycle_1.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(bicycle_1) + + # Spawn the second bicycle + second_wp = self._move_waypoint_forward(first_wp, self._obstacle_distance) + bicycle_2 = self._spawn_obstacle(second_wp, rng.choice(self._bicycle_bps)) + + wps = second_wp.next(self._bicycle_drive_distance) + if not wps: + raise ValueError("Couldn't find an end location for the bicycles") + self._target_locs.append(wps[0].transform.location) + + # Set its initial conditions + bicycle_2.apply_control(carla.VehicleControl(hand_brake=True)) + self.other_actors.append(bicycle_2) + + def _create_behavior(self): + """ + Activate the bicycles and wait for the ego to be close-by before changing the side traffic. + End condition is based on the ego behind in front of the bicycles, or timeout based. + """ + root = py_trees.composites.Sequence(name="HazardAtSideLane") + if self.route_mode: + total_dist = self._distance + self._obstacle_distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + root.add_child(ChangeRoadBehavior(extra_space=self._extra_space)) + + main_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + main_behavior.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + # End condition + end_condition = py_trees.composites.Sequence(name="End Condition") + end_condition.add_child(WaitUntilInFront(self.ego_vehicles[0], self.other_actors[-1], check_distance=False)) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + main_behavior.add_child(end_condition) + + # Bicycle movement. Move them for a set distance, then stop + offset = self._offset * self._starting_wp.lane_width / 2 + opt_dict = {'offset': offset} + for actor, target_loc in zip(self.other_actors, self._target_locs): + bicycle = py_trees.composites.Sequence(name="Bicycle behavior") + bicycle.add_child(BasicAgentBehavior(actor, target_loc, target_speed=self._bicycle_speed, opt_dict=opt_dict)) + bicycle.add_child(HandBrakeVehicle(actor, 1)) # In case of collisions + bicycle.add_child(WaitForever()) # Don't make the bicycle stop the parallel behavior + main_behavior.add_child(bicycle) + + behavior = py_trees.composites.Sequence(name="Side lane behavior") + behavior.add_child(InTriggerDistanceToVehicle( + self.ego_vehicles[0], self.other_actors[0], self._trigger_distance)) + behavior.add_child(Idle(self._wait_duration)) + if self.route_mode: + behavior.add_child(SetMaxSpeed(self._max_speed)) + behavior.add_child(WaitForever()) + + main_behavior.add_child(behavior) + + root.add_child(main_behavior) + if self.route_mode: + root.add_child(SetMaxSpeed(0)) + root.add_child(ChangeRoadBehavior(extra_space=0)) + + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() + + +class HazardAtSideLaneTwoWays(HazardAtSideLane): + """ + Variation of the HazardAtSideLane scenario but the ego now has to invade the opposite lane + """ + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=180): + + self._opposite_frequency = get_value_parameter(config, 'frequency', float, 100) + + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _create_behavior(self): + """ + Activate the bicycles and wait for the ego to be close-by before changing the opposite traffic. + End condition is based on the ego behind in front of the bicycles, or timeout based. + """ + + root = py_trees.composites.Sequence(name="HazardAtSideLaneTwoWays") + if self.route_mode: + total_dist = self._distance + self._obstacle_distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + root.add_child(ChangeRoadBehavior(extra_space=self._extra_space)) + + main_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + main_behavior.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + # End condition + end_condition = py_trees.composites.Sequence(name="End Condition") + end_condition.add_child(WaitUntilInFront(self.ego_vehicles[0], self.other_actors[-1], check_distance=False)) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + main_behavior.add_child(end_condition) + + # Bicycle movement. Move them for a set distance, then stop + offset = self._offset * self._starting_wp.lane_width / 2 + opt_dict = {'offset': offset} + for actor, target_loc in zip(self.other_actors, self._target_locs): + bicycle = py_trees.composites.Sequence(name="Bicycle behavior") + bicycle.add_child(BasicAgentBehavior(actor, target_loc, target_speed=self._bicycle_speed, opt_dict=opt_dict)) + bicycle.add_child(HandBrakeVehicle(actor, 1)) # In case of collisions + bicycle.add_child(WaitForever()) # Don't make the bicycle stop the parallel behavior + main_behavior.add_child(bicycle) + + behavior = py_trees.composites.Sequence(name="Side lane behavior") + behavior.add_child(InTriggerDistanceToVehicle( + self.ego_vehicles[0], self.other_actors[0], self._trigger_distance)) + behavior.add_child(Idle(self._wait_duration)) + if self.route_mode: + behavior.add_child(SwitchWrongDirectionTest(False)) + behavior.add_child(ChangeOppositeBehavior(spawn_dist=self._opposite_frequency)) + behavior.add_child(WaitForever()) + + main_behavior.add_child(behavior) + + root.add_child(main_behavior) + if self.route_mode: + root.add_child(SwitchWrongDirectionTest(False)) + root.add_child(ChangeOppositeBehavior(spawn_dist=40)) + root.add_child(ChangeRoadBehavior(extra_space=0)) + + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + + return root diff --git a/srunner/scenarios/route_scenario.py b/srunner/scenarios/route_scenario.py index 962a0d834..9835f1109 100644 --- a/srunner/scenarios/route_scenario.py +++ b/srunner/scenarios/route_scenario.py @@ -11,131 +11,44 @@ from __future__ import print_function -import math +import glob +import os +import sys +import importlib +import inspect import traceback -import xml.etree.ElementTree as ET - import py_trees +from numpy import random import carla from agents.navigation.local_planner import RoadOption -# pylint: disable=line-too-long -from srunner.scenarioconfigs.scenario_configuration import ScenarioConfiguration, ActorConfigurationData -# pylint: enable=line-too-long +from srunner.scenarioconfigs.scenario_configuration import ActorConfigurationData from srunner.scenariomanager.carla_data_provider import CarlaDataProvider -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import Idle, ScenarioTriggerer -from srunner.scenarios.basic_scenario import BasicScenario -from srunner.tools.route_parser import RouteParser, TRIGGER_THRESHOLD, TRIGGER_ANGLE_THRESHOLD -from srunner.tools.route_manipulation import interpolate_trajectory -from srunner.tools.py_trees_port import oneshot_behavior - -from srunner.scenarios.control_loss import ControlLoss -from srunner.scenarios.follow_leading_vehicle import FollowLeadingVehicleRoute -from srunner.scenarios.object_crash_vehicle import DynamicObjectCrossing -from srunner.scenarios.object_crash_intersection import VehicleTurningRoute -from srunner.scenarios.other_leading_vehicle import OtherLeadingVehicle -from srunner.scenarios.maneuver_opposite_direction import ManeuverOppositeDirection -from srunner.scenarios.junction_crossing_route import NoSignalJunctionCrossingRoute -from srunner.scenarios.signalized_junction_left_turn import SignalizedJunctionLeftTurn -from srunner.scenarios.signalized_junction_right_turn import SignalizedJunctionRightTurn -from srunner.scenarios.opposite_vehicle_taking_priority import OppositeVehicleRunningRedLight -from srunner.scenarios.background_activity import BackgroundActivity +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ScenarioTriggerer, Idle +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitForBlackboardVariable from srunner.scenariomanager.scenarioatomics.atomic_criteria import (CollisionTest, InRouteTest, RouteCompletionTest, OutsideRouteLanesTest, RunningRedLightTest, RunningStopTest, - ActorSpeedAboveThresholdTest) - -SECONDS_GIVEN_PER_METERS = 0.4 - -NUMBER_CLASS_TRANSLATION = { - "Scenario1": ControlLoss, - "Scenario2": FollowLeadingVehicleRoute, - "Scenario3": DynamicObjectCrossing, - "Scenario4": VehicleTurningRoute, - "Scenario5": OtherLeadingVehicle, - "Scenario6": ManeuverOppositeDirection, - "Scenario7": OppositeVehicleRunningRedLight, - "Scenario8": SignalizedJunctionLeftTurn, - "Scenario9": SignalizedJunctionRightTurn, - "Scenario10": NoSignalJunctionCrossingRoute -} - - -def convert_json_to_transform(actor_dict): - """ - Convert a JSON string to a CARLA transform - """ - return carla.Transform(location=carla.Location(x=float(actor_dict['x']), y=float(actor_dict['y']), - z=float(actor_dict['z'])), - rotation=carla.Rotation(roll=0.0, pitch=0.0, yaw=float(actor_dict['yaw']))) - - -def convert_json_to_actor(actor_dict): - """ - Convert a JSON string to an ActorConfigurationData dictionary - """ - node = ET.Element('waypoint') - node.set('x', actor_dict['x']) - node.set('y', actor_dict['y']) - node.set('z', actor_dict['z']) - node.set('yaw', actor_dict['yaw']) - - return ActorConfigurationData.parse_from_node(node, 'simulation') - - -def convert_transform_to_location(transform_vec): - """ - Convert a vector of transforms to a vector of locations - """ - location_vec = [] - for transform_tuple in transform_vec: - location_vec.append((transform_tuple[0].location, transform_tuple[1])) - - return location_vec - - -def compare_scenarios(scenario_choice, existent_scenario): - """ - Compare function for scenarios based on distance of the scenario start position - """ - def transform_to_pos_vec(scenario): - """ - Convert left/right/front to a meaningful CARLA position - """ - position_vec = [scenario['trigger_position']] - if scenario['other_actors'] is not None: - if 'left' in scenario['other_actors']: - position_vec += scenario['other_actors']['left'] - if 'front' in scenario['other_actors']: - position_vec += scenario['other_actors']['front'] - if 'right' in scenario['other_actors']: - position_vec += scenario['other_actors']['right'] - - return position_vec + ActorBlockedTest, + MinimumSpeedRouteTest) - # put the positions of the scenario choice into a vec of positions to be able to compare +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.scenarios.background_activity import BackgroundBehavior +from srunner.scenariomanager.weather_sim import RouteWeatherBehavior +from srunner.scenariomanager.lights_sim import RouteLightsBehavior +from srunner.scenariomanager.timer import RouteTimeoutBehavior - choice_vec = transform_to_pos_vec(scenario_choice) - existent_vec = transform_to_pos_vec(existent_scenario) - for pos_choice in choice_vec: - for pos_existent in existent_vec: +from srunner.tools.route_parser import RouteParser, DIST_THRESHOLD +from srunner.tools.route_manipulation import interpolate_trajectory - dx = float(pos_choice['x']) - float(pos_existent['x']) - dy = float(pos_choice['y']) - float(pos_existent['y']) - dz = float(pos_choice['z']) - float(pos_existent['z']) - dist_position = math.sqrt(dx * dx + dy * dy + dz * dz) - dyaw = float(pos_choice['yaw']) - float(pos_choice['yaw']) - dist_angle = math.sqrt(dyaw * dyaw) - if dist_position < TRIGGER_THRESHOLD and dist_angle < TRIGGER_ANGLE_THRESHOLD: - return True - return False +SECONDS_GIVEN_PER_METERS = 0.4 class RouteScenario(BasicScenario): @@ -151,69 +64,61 @@ def __init__(self, world, config, debug_mode=False, criteria_enable=True, timeou """ self.config = config - self.route = None - self.sampled_scenarios_definitions = None - - self._update_route(world, config, debug_mode) + self.route = self._get_route(config) + sampled_scenario_definitions = self._filter_scenarios(config.scenario_configs) - ego_vehicle = self._update_ego_vehicle() + ego_vehicle = self._spawn_ego_vehicle() + self.timeout = self._estimate_route_timeout() - self.list_scenarios = self._build_scenario_instances(world, - ego_vehicle, - self.sampled_scenarios_definitions, - scenarios_per_tick=5, - timeout=self.timeout, - debug_mode=debug_mode) + if debug_mode: + self._draw_waypoints(world, self.route, vertical_shift=0.1, size=0.1, persistency=self.timeout, downsample=5) - self.list_scenarios.append(BackgroundActivity( - world, ego_vehicle, self.config, self.route, timeout=self.timeout)) + self._build_scenarios( + world, ego_vehicle, sampled_scenario_definitions, timeout=self.timeout, debug=debug_mode > 0 + ) - super(RouteScenario, self).__init__(name=config.name, - ego_vehicles=[ego_vehicle], - config=config, - world=world, - debug_mode=False, - terminate_on_failure=False, - criteria_enable=criteria_enable) + super(RouteScenario, self).__init__( + config.name, [ego_vehicle], config, world, debug_mode > 1, False, criteria_enable + ) - def _update_route(self, world, config, debug_mode): + def _get_route(self, config): """ - Update the input route, i.e. refine waypoint list, and extract possible scenario locations + Gets the route from the configuration, interpolating it to the desired density, + saving it to the CarlaDataProvider and sending it to the agent Parameters: - world: CARLA world - config: Scenario configuration (RouteConfiguration) + - debug_mode: boolean to decide whether or not the route poitns are printed """ - - # Transform the scenario file into a dictionary - world_annotations = RouteParser.parse_annotations_file(config.scenario_file) - # prepare route's trajectory (interpolate and add the GPS route) - gps_route, route = interpolate_trajectory(config.trajectory) - - potential_scenarios_definitions, _ = RouteParser.scan_route_for_scenarios(config.town, route, world_annotations) + gps_route, route = interpolate_trajectory(config.keypoints) + if config.agent is not None: + config.agent.set_global_plan(gps_route, route) - self.route = route - CarlaDataProvider.set_ego_vehicle_route(convert_transform_to_location(self.route)) + return route - if config.agent is not None: - config.agent.set_global_plan(gps_route, self.route) + def _filter_scenarios(self, scenario_configs): + """ + Given a list of scenarios, filters out does that don't make sense to be triggered, + as they are either too far from the route or don't fit with the route shape - # Sample the scenarios to be used for this route instance. - self.sampled_scenarios_definitions = self._scenario_sampling(potential_scenarios_definitions) + Parameters: + - scenario_configs: list of ScenarioConfiguration + """ + new_scenarios_config = [] + for scenario_config in scenario_configs: + trigger_point = scenario_config.trigger_points[0] + if not RouteParser.is_scenario_at_route(trigger_point, self.route): + print("WARNING: Ignoring scenario '{}' as it is too far from the route".format(scenario_config.name)) + continue - # Timeout of scenario in seconds - self.timeout = self._estimate_route_timeout() + new_scenarios_config.append(scenario_config) - # Print route in debug mode - if debug_mode: - self._draw_waypoints(world, self.route, vertical_shift=0.1, persistency=50000.0) + return new_scenarios_config - def _update_ego_vehicle(self): - """ - Set/Update the start position of the ego_vehicle - """ - # move ego to correct position + def _spawn_ego_vehicle(self): + """Spawn the ego vehicle at the first waypoint of the route""" elevate_transform = self.route[0][0] elevate_transform.location.z += 0.5 @@ -225,7 +130,7 @@ def _update_ego_vehicle(self): def _estimate_route_timeout(self): """ - Estimate the duration of the route + Estimate the duration of the route, as a proportinal value of its length """ route_length = 0.0 # in meters @@ -237,162 +142,111 @@ def _estimate_route_timeout(self): return int(SECONDS_GIVEN_PER_METERS * route_length) - # pylint: disable=no-self-use - def _draw_waypoints(self, world, waypoints, vertical_shift, persistency=-1): + def _draw_waypoints(self, world, waypoints, vertical_shift, size, persistency=-1, downsample=1): """ Draw a list of waypoints at a certain height given in vertical_shift. """ - for w in waypoints: + for i, w in enumerate(waypoints): + if i % downsample != 0: + continue + wp = w[0].location + carla.Location(z=vertical_shift) if w[1] == RoadOption.LEFT: # Yellow - color = carla.Color(255, 255, 0) + color = carla.Color(128, 128, 0) elif w[1] == RoadOption.RIGHT: # Cyan - color = carla.Color(0, 255, 255) + color = carla.Color(0, 128, 128) elif w[1] == RoadOption.CHANGELANELEFT: # Orange - color = carla.Color(255, 64, 0) + color = carla.Color(128, 32, 0) elif w[1] == RoadOption.CHANGELANERIGHT: # Dark Cyan - color = carla.Color(0, 64, 255) + color = carla.Color(0, 32, 128) elif w[1] == RoadOption.STRAIGHT: # Gray - color = carla.Color(128, 128, 128) + color = carla.Color(64, 64, 64) else: # LANEFOLLOW - color = carla.Color(0, 255, 0) # Green + color = carla.Color(0, 128, 0) # Green world.debug.draw_point(wp, size=0.1, color=color, life_time=persistency) - world.debug.draw_point(waypoints[0][0].location + carla.Location(z=vertical_shift), size=0.2, - color=carla.Color(0, 0, 255), life_time=persistency) - world.debug.draw_point(waypoints[-1][0].location + carla.Location(z=vertical_shift), size=0.2, - color=carla.Color(255, 0, 0), life_time=persistency) - - def _scenario_sampling(self, potential_scenarios_definitions): - """ - The function used to sample the scenarios that are going to happen for this route. - """ - - # fix the random seed for reproducibility - rng = CarlaDataProvider.get_random_seed() + world.debug.draw_point(waypoints[0][0].location + carla.Location(z=vertical_shift), size=2*size, + color=carla.Color(0, 0, 128), life_time=persistency) + world.debug.draw_point(waypoints[-1][0].location + carla.Location(z=vertical_shift), size=2*size, + color=carla.Color(128, 128, 128), life_time=persistency) - def position_sampled(scenario_choice, sampled_scenarios): - """ - Check if a position was already sampled, i.e. used for another scenario - """ - for existent_scenario in sampled_scenarios: - # If the scenarios have equal positions then it is true. - if compare_scenarios(scenario_choice, existent_scenario): - return True + def _scenario_sampling(self, potential_scenarios, random_seed=0): + """Sample the scenarios that are going to happen for this route.""" + # Fix the random seed for reproducibility, and randomly sample a scenario per trigger position. + rng = random.RandomState(random_seed) - return False - - # The idea is to randomly sample a scenario per trigger position. sampled_scenarios = [] - for trigger in potential_scenarios_definitions.keys(): - possible_scenarios = potential_scenarios_definitions[trigger] - - scenario_choice = rng.choice(possible_scenarios) - del possible_scenarios[possible_scenarios.index(scenario_choice)] - # We keep sampling and testing if this position is present on any of the scenarios. - while position_sampled(scenario_choice, sampled_scenarios): - if possible_scenarios is None or not possible_scenarios: - scenario_choice = None - break - scenario_choice = rng.choice(possible_scenarios) - del possible_scenarios[possible_scenarios.index(scenario_choice)] - - if scenario_choice is not None: - sampled_scenarios.append(scenario_choice) + for trigger in list(potential_scenarios): + scenario_list = potential_scenarios[trigger] + sampled_scenarios.append(rng.choice(scenario_list)) return sampled_scenarios - def _build_scenario_instances(self, world, ego_vehicle, scenario_definitions, - scenarios_per_tick=5, timeout=300, debug_mode=False): - """ - Based on the parsed route and possible scenarios, build all the scenario classes. - """ - scenario_instance_vec = [] + def get_all_scenario_classes(self): - if debug_mode: - for scenario in scenario_definitions: - loc = carla.Location(scenario['trigger_position']['x'], - scenario['trigger_position']['y'], - scenario['trigger_position']['z']) + carla.Location(z=2.0) - world.debug.draw_point(loc, size=0.3, color=carla.Color(255, 0, 0), life_time=100000) - world.debug.draw_string(loc, str(scenario['name']), draw_shadow=False, - color=carla.Color(0, 0, 255), life_time=100000, persistent_lines=True) - - for scenario_number, definition in enumerate(scenario_definitions): - # Get the class possibilities for this scenario number - scenario_class = NUMBER_CLASS_TRANSLATION[definition['name']] - - # Create the other actors that are going to appear - if definition['other_actors'] is not None: - list_of_actor_conf_instances = self._get_actors_instances(definition['other_actors']) - else: - list_of_actor_conf_instances = [] - # Create an actor configuration for the ego-vehicle trigger position - - egoactor_trigger_position = convert_json_to_transform(definition['trigger_position']) - scenario_configuration = ScenarioConfiguration() - scenario_configuration.other_actors = list_of_actor_conf_instances - scenario_configuration.trigger_points = [egoactor_trigger_position] - scenario_configuration.subtype = definition['scenario_type'] - scenario_configuration.ego_vehicles = [ActorConfigurationData('vehicle.lincoln.mkz_2017', - ego_vehicle.get_transform(), - 'hero')] - route_var_name = "ScenarioRouteNumber{}".format(scenario_number) - scenario_configuration.route_var_name = route_var_name + # Path of all scenario at "srunner/scenarios" folder + scenarios_list = glob.glob("{}/srunner/scenarios/*.py".format(os.getenv('SCENARIO_RUNNER_ROOT', "./"))) - try: - scenario_instance = scenario_class(world, [ego_vehicle], scenario_configuration, - criteria_enable=False, timeout=timeout) - # Do a tick every once in a while to avoid spawning everything at the same time - if scenario_number % scenarios_per_tick == 0: - if CarlaDataProvider.is_sync_mode(): - world.tick() - else: - world.wait_for_tick() - - scenario_number += 1 - except Exception as e: # pylint: disable=broad-except - if debug_mode: - traceback.print_exc() - print("Skipping scenario '{}' due to setup error: {}".format(definition['name'], e)) - continue + all_scenario_classes = {} - scenario_instance_vec.append(scenario_instance) + for scenario_file in scenarios_list: - return scenario_instance_vec + # Get their module + module_name = os.path.basename(scenario_file).split('.')[0] + sys.path.insert(0, os.path.dirname(scenario_file)) + scenario_module = importlib.import_module(module_name) - def _get_actors_instances(self, list_of_antagonist_actors): + # And their members of type class + for member in inspect.getmembers(scenario_module, inspect.isclass): + # TODO: Filter out any class that isn't a child of BasicScenario + all_scenario_classes[member[0]] = member[1] + + return all_scenario_classes + + def _build_scenarios(self, world, ego_vehicle, scenario_definitions, scenarios_per_tick=5, timeout=300, debug=False): """ - Get the full list of actor instances. + Initializes the class of all the scenarios that will be present in the route. + If a class fails to be initialized, a warning is printed but the route execution isn't stopped """ + all_scenario_classes = self.get_all_scenario_classes() + self.list_scenarios = [] + ego_data = ActorConfigurationData(ego_vehicle.type_id, ego_vehicle.get_transform(), 'hero') + + if debug: + tmap = CarlaDataProvider.get_map() + for scenario_config in scenario_definitions: + scenario_loc = scenario_config.trigger_points[0].location + debug_loc = tmap.get_waypoint(scenario_loc).transform.location + carla.Location(z=0.2) + world.debug.draw_point(debug_loc, size=0.2, color=carla.Color(128, 0, 0), life_time=timeout) + world.debug.draw_string(debug_loc, str(scenario_config.name), draw_shadow=False, + color=carla.Color(0, 0, 128), life_time=timeout, persistent_lines=True) + + for scenario_number, scenario_config in enumerate(scenario_definitions): + scenario_config.ego_vehicles = [ego_data] + scenario_config.route_var_name = "ScenarioRouteNumber{}".format(scenario_number) + scenario_config.route = self.route - def get_actors_from_list(list_of_actor_def): - """ - Receives a list of actor definitions and creates an actual list of ActorConfigurationObjects - """ - sublist_of_actors = [] - for actor_def in list_of_actor_def: - sublist_of_actors.append(convert_json_to_actor(actor_def)) - - return sublist_of_actors + try: + scenario_class = all_scenario_classes[scenario_config.type] + scenario_instance = scenario_class(world, [ego_vehicle], scenario_config, timeout=timeout) - list_of_actors = [] - # Parse vehicles to the left - if 'front' in list_of_antagonist_actors: - list_of_actors += get_actors_from_list(list_of_antagonist_actors['front']) + # Do a tick every once in a while to avoid spawning everything at the same time + if scenario_number % scenarios_per_tick == 0: + world.tick() - if 'left' in list_of_antagonist_actors: - list_of_actors += get_actors_from_list(list_of_antagonist_actors['left']) + except Exception as e: + if not debug: + print("Skipping scenario '{}' due to setup error: {}".format(scenario_config.type, e)) + else: + traceback.print_exc() + continue - if 'right' in list_of_antagonist_actors: - list_of_actors += get_actors_from_list(list_of_antagonist_actors['right']) + self.list_scenarios.append(scenario_instance) - return list_of_actors # pylint: enable=no-self-use - def _initialize_actors(self, config): """ Set other_actors to the superset of all scenario actors @@ -403,85 +257,125 @@ def _initialize_actors(self, config): def _create_behavior(self): """ - Basic behavior do nothing, i.e. Idle - """ - scenario_trigger_distance = 1.5 # Max trigger distance between route and scenario + Creates a parallel behavior that runs all of the scenarios part of the route. + These subbehaviors have had a trigger condition added so that they wait until + the agent is close to their trigger point before activating. - behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + It also adds the BackgroundActivity scenario, which will be active throughout the whole route. + This behavior never ends and the end condition is given by the RouteCompletionTest criterion. + """ + scenario_trigger_distance = DIST_THRESHOLD # Max trigger distance between route and scenario - subbehavior = py_trees.composites.Parallel(name="Behavior", - policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL) + behavior = py_trees.composites.Parallel(name="Route Behavior", + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL) scenario_behaviors = [] blackboard_list = [] - for i, scenario in enumerate(self.list_scenarios): - if scenario.scenario.behavior is not None: - route_var_name = scenario.config.route_var_name - if route_var_name is not None: - scenario_behaviors.append(scenario.scenario.behavior) - blackboard_list.append([scenario.config.route_var_name, - scenario.config.trigger_points[0].location]) - else: - name = "{} - {}".format(i, scenario.scenario.behavior.name) - oneshot_idiom = oneshot_behavior(name, - behaviour=scenario.scenario.behavior, - name=name) - scenario_behaviors.append(oneshot_idiom) + for scenario in self.list_scenarios: + if scenario.behavior_tree is not None: + scenario_behaviors.append(scenario.behavior_tree) + blackboard_list.append([scenario.config.route_var_name, + scenario.config.trigger_points[0].location]) - # Add behavior that manages the scenarios trigger conditions + # Add the behavior that manages the scenario trigger conditions scenario_triggerer = ScenarioTriggerer( - self.ego_vehicles[0], - self.route, - blackboard_list, - scenario_trigger_distance, - repeat_scenarios=False - ) + self.ego_vehicles[0], self.route, blackboard_list, scenario_trigger_distance) + behavior.add_child(scenario_triggerer) # Tick the ScenarioTriggerer before the scenarios - subbehavior.add_child(scenario_triggerer) # make ScenarioTriggerer the first thing to be checked - subbehavior.add_children(scenario_behaviors) - subbehavior.add_child(Idle()) # The behaviours cannot make the route scenario stop - behavior.add_child(subbehavior) + # Add the Background Activity + behavior.add_child(BackgroundBehavior(self.ego_vehicles[0], self.route, name="BackgroundActivity")) + behavior.add_children(scenario_behaviors) return behavior def _create_test_criteria(self): """ + Create the criteria tree. It starts with some route criteria (which are always active), + and adds the scenario specific ones, which will only be active during their scenario """ + criteria = py_trees.composites.Parallel(name="Criteria", + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + + # End condition + criteria.add_child(RouteCompletionTest(self.ego_vehicles[0], route=self.route)) + + # 'Normal' criteria + criteria.add_child(OutsideRouteLanesTest(self.ego_vehicles[0], route=self.route)) + criteria.add_child(CollisionTest(self.ego_vehicles[0], name="CollisionTest")) + criteria.add_child(RunningRedLightTest(self.ego_vehicles[0])) + criteria.add_child(RunningStopTest(self.ego_vehicles[0])) + criteria.add_child(MinimumSpeedRouteTest(self.ego_vehicles[0], route=self.route, checkpoints=4, name="MinSpeedTest")) + + # These stop the route early to save computational time + criteria.add_child(InRouteTest( + self.ego_vehicles[0], route=self.route, offroad_max=30, terminate_on_failure=True)) + criteria.add_child(ActorBlockedTest( + self.ego_vehicles[0], min_speed=0.1, max_time=180.0, terminate_on_failure=True, name="AgentBlockedTest") + ) - criteria = [] + for scenario in self.list_scenarios: + scenario_criteria = scenario.get_criteria() + if len(scenario_criteria) == 0: + continue # No need to create anything - route = convert_transform_to_location(self.route) + criteria.add_child( + self._create_criterion_tree(scenario, scenario_criteria) + ) + + return criteria - collision_criterion = CollisionTest(self.ego_vehicles[0], terminate_on_failure=False) + def _create_weather_behavior(self): + """ + Create the weather behavior + """ + if len(self.config.weather) == 1: + return # Just set the weather at the beginning and done + return RouteWeatherBehavior(self.ego_vehicles[0], self.route, self.config.weather) - route_criterion = InRouteTest(self.ego_vehicles[0], - route=route, - offroad_max=30, - terminate_on_failure=True) + def _create_lights_behavior(self): + """ + Create the street lights behavior + """ + return RouteLightsBehavior(self.ego_vehicles[0], 100) - completion_criterion = RouteCompletionTest(self.ego_vehicles[0], route=route) + def _create_timeout_behavior(self): + """ + Create the timeout behavior + """ + return RouteTimeoutBehavior(self.ego_vehicles[0], self.route) - outsidelane_criterion = OutsideRouteLanesTest(self.ego_vehicles[0], route=route) + def _initialize_environment(self, world): + """ + Set the weather + """ + # Set the appropriate weather conditions + world.set_weather(self.config.weather[0][1]) - red_light_criterion = RunningRedLightTest(self.ego_vehicles[0]) + def _create_criterion_tree(self, scenario, criteria): + """ + We can make use of the blackboard variables used by the behaviors themselves, + as we already have an atomic that handles their (de)activation. + The criteria will wait until that variable is active (the scenario has started), + and will automatically stop when it deactivates (as the scenario has finished) + """ + scenario_name = scenario.name + var_name = scenario.config.route_var_name + check_name = "WaitForBlackboardVariable: {}".format(var_name) - stop_criterion = RunningStopTest(self.ego_vehicles[0]) + criteria_tree = py_trees.composites.Sequence(name=scenario_name) + criteria_tree.add_child(WaitForBlackboardVariable(var_name, True, False, name=check_name)) - blocked_criterion = ActorSpeedAboveThresholdTest(self.ego_vehicles[0], - speed_threshold=0.1, - below_threshold_max_time=90.0, - terminate_on_failure=True) + scenario_criteria = py_trees.composites.Parallel(name=scenario_name, + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + for criterion in criteria: + scenario_criteria.add_child(criterion) + scenario_criteria.add_child(WaitForBlackboardVariable(var_name, False, None, name=check_name)) - criteria.append(completion_criterion) - criteria.append(collision_criterion) - criteria.append(route_criterion) - criteria.append(outsidelane_criterion) - criteria.append(red_light_criterion) - criteria.append(stop_criterion) - criteria.append(blocked_criterion) + criteria_tree.add_child(scenario_criteria) + criteria_tree.add_child(Idle()) # Avoid the indivual criteria stopping the simulation + return criteria_tree - return criteria def __del__(self): """ diff --git a/srunner/scenarios/signalized_junction_left_turn.py b/srunner/scenarios/signalized_junction_left_turn.py index 2e04497ad..0bb4ae308 100644 --- a/srunner/scenarios/signalized_junction_left_turn.py +++ b/srunner/scenarios/signalized_junction_left_turn.py @@ -15,29 +15,39 @@ import carla from srunner.scenariomanager.carla_data_provider import CarlaDataProvider -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ActorFlow, TrafficLightFreezer -from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitEndIntersection -from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ActorFlow, TrafficLightFreezer, ScenarioTimeout +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitEndIntersection, DriveDistance +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest from srunner.scenarios.basic_scenario import BasicScenario from srunner.tools.scenario_helper import (generate_target_waypoint, get_junction_topology, filter_junction_wp_direction, + get_same_dir_lanes, get_closest_traffic_light) -from srunner.tools.background_manager import Scenario8Manager +from srunner.tools.background_manager import HandleJunctionScenario, ChangeOppositeBehavior +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default -class SignalizedJunctionLeftTurn(BasicScenario): +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default - """ - Implementation class for Hero - Vehicle turning left at signalized junction scenario, - Traffic Scenario 08. - This is a single ego vehicle scenario +class JunctionLeftTurn(BasicScenario): + """ + Vehicle turning left at junction scenario, with actors coming in the opposite direction. + The ego has to react to them, safely crossing the opposite lane """ - - timeout = 80 # Timeout of scenario in seconds def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, timeout=80): @@ -46,20 +56,31 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals """ self._world = world self._map = CarlaDataProvider.get_map() - self._source_dist = 40 - self._sink_dist = 20 - self._source_dist_interval = [25, 50] - self._opposite_speed = 35 / 3.6 - self._rng = random.RandomState(2000) - self._green_light_delay = 5 # Wait before the ego's lane traffic light turns green - self._direction = 'opposite' + self._rng = CarlaDataProvider.get_random_seed() + self.timeout = timeout - super(SignalizedJunctionLeftTurn, self).__init__("SignalizedJunctionLeftTurn", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) + + self._direction = 'opposite' + + self._green_light_delay = 5 # Wait before the ego's lane traffic light turns green + self._flow_tl_dict = {} + self._init_tl_dict = {} + self._end_distance = 10 + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 20) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [25, 50]) + self._scenario_timeout = 240 + + # The faster the flow, the further they are spawned, leaving time to react to them + self._source_dist = 4 * self._flow_speed + self._sink_dist = 2.5 * self._flow_speed + + super().__init__("JunctionLeftTurn", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) def _initialize_actors(self, config): """ @@ -67,19 +88,21 @@ def _initialize_actors(self, config): Override this method in child class to provide custom initialization. """ ego_location = config.trigger_points[0].location - ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) + self._ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) # Get the junction - starting_wp = ego_wp + starting_wp = self._ego_wp + ego_junction_dist = 0 while not starting_wp.is_junction: starting_wps = starting_wp.next(1.0) if len(starting_wps) == 0: raise ValueError("Failed to find junction as a waypoint with no next was detected") starting_wp = starting_wps[0] - junction = starting_wp.get_junction() + ego_junction_dist += 1 + self._junction = starting_wp.get_junction() # Get the opposite entry lane wp - entry_wps, _ = get_junction_topology(junction) + entry_wps, _ = get_junction_topology(self._junction) source_entry_wps = filter_junction_wp_direction(starting_wp, entry_wps, self._direction) if not source_entry_wps: raise ValueError("Trying to find a lane in the {} direction but none was found".format(self._direction)) @@ -89,15 +112,15 @@ def _initialize_actors(self, config): # Get the source transform source_wp = source_entry_wp - accum_dist = 0 - while accum_dist < self._source_dist: + source_junction_dist = 0 + while source_junction_dist < self._source_dist: source_wps = source_wp.previous(5) if len(source_wps) == 0: raise ValueError("Failed to find a source location as a waypoint with no previous was detected") if source_wps[0].is_junction: break source_wp = source_wps[0] - accum_dist += 5 + source_junction_dist += 5 self._source_wp = source_wp source_transform = self._source_wp.transform @@ -109,17 +132,55 @@ def _initialize_actors(self, config): raise ValueError("Failed to find a sink location as a waypoint with no next was detected") self._sink_wp = sink_wps[0] - # get traffic lights - tls = self._world.get_traffic_lights_in_junction(junction.id) - ego_tl = get_closest_traffic_light(ego_wp, tls) + def _create_behavior(self): + raise NotImplementedError("Found missing behavior") + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() + + +class SignalizedJunctionLeftTurn(JunctionLeftTurn): + """ + Signalized version of 'JunctionLeftTurn` + """ + + timeout = 80 # Timeout of scenario in seconds + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=80): + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _initialize_actors(self, config): + """ + Default initialization of other actors. + Override this method in child class to provide custom initialization. + """ + super()._initialize_actors(config) + + tls = self._world.get_traffic_lights_in_junction(self._junction.id) + if not tls: + raise ValueError("Found no traffic lights, use the non signalized version instead") + ego_tl = get_closest_traffic_light(self._ego_wp, tls) source_tl = get_closest_traffic_light(self._source_wp, tls) - self._flow_tl_dict = {} - self._init_tl_dict = {} + for tl in tls: - if tl == ego_tl: + if tl.id == ego_tl.id: self._flow_tl_dict[tl] = carla.TrafficLightState.Green self._init_tl_dict[tl] = carla.TrafficLightState.Red - elif tl == source_tl: + elif tl.id == source_tl.id: self._flow_tl_dict[tl] = carla.TrafficLightState.Green self._init_tl_dict[tl] = carla.TrafficLightState.Green else: @@ -131,33 +192,80 @@ def _create_behavior(self): Hero vehicle is turning left in an urban area at a signalized intersection, where, a flow of actors coming straight is present. """ + sequence = py_trees.composites.Sequence(name="SignalizedJunctionLeftTurn") + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=get_same_dir_lanes(self._source_wp), + remove_exits=get_same_dir_lanes(self._sink_wp), + stop_entries=False, + extend_road_exit=self._sink_dist + 20 + )) + sequence.add_child(ChangeOppositeBehavior(active=False)) root = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) - root.add_child(WaitEndIntersection(self.ego_vehicles[0])) + end_condition = py_trees.composites.Sequence() + end_condition.add_child(WaitEndIntersection(self.ego_vehicles[0])) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + root.add_child(end_condition) root.add_child(ActorFlow( - self._source_wp, self._sink_wp, self._source_dist_interval, 2, self._opposite_speed)) + self._source_wp, self._sink_wp, self._source_dist_interval, 2, self._flow_speed)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) tl_freezer_sequence = py_trees.composites.Sequence("Traffic Light Behavior") tl_freezer_sequence.add_child(TrafficLightFreezer(self._init_tl_dict, duration=self._green_light_delay)) tl_freezer_sequence.add_child(TrafficLightFreezer(self._flow_tl_dict)) root.add_child(tl_freezer_sequence) - sequence = py_trees.composites.Sequence("Sequence Behavior") - if CarlaDataProvider.get_ego_vehicle_route(): - sequence.add_child(Scenario8Manager(self._direction)) sequence.add_child(root) + if self.route_mode: + sequence.add_child(ChangeOppositeBehavior(active=True)) + return sequence - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - return [CollisionTest(self.ego_vehicles[0])] - def __del__(self): +class NonSignalizedJunctionLeftTurn(JunctionLeftTurn): + """ + Non signalized version of 'JunctionLeftTurn` + """ + + timeout = 80 # Timeout of scenario in seconds + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=80): + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _create_behavior(self): """ - Remove all actors upon deletion + Hero vehicle is turning left in an urban area at a signalized intersection, + where, a flow of actors coming straight is present. """ - self.remove_all_actors() + sequence = py_trees.composites.Sequence(name="NonSignalizedJunctionLeftTurn") + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=get_same_dir_lanes(self._source_wp), + remove_exits=get_same_dir_lanes(self._sink_wp), + stop_entries=True, + extend_road_exit=self._sink_dist + 20 + )) + sequence.add_child(ChangeOppositeBehavior(active=False)) + + root = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition = py_trees.composites.Sequence() + end_condition.add_child(WaitEndIntersection(self.ego_vehicles[0])) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + root.add_child(end_condition) + root.add_child(ActorFlow( + self._source_wp, self._sink_wp, self._source_dist_interval, 2, self._flow_speed)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + sequence.add_child(root) + + if self.route_mode: + sequence.add_child(ChangeOppositeBehavior(active=True)) + + return sequence diff --git a/srunner/scenarios/signalized_junction_right_turn.py b/srunner/scenarios/signalized_junction_right_turn.py index 47e855c8d..f8843ec5d 100644 --- a/srunner/scenarios/signalized_junction_right_turn.py +++ b/srunner/scenarios/signalized_junction_right_turn.py @@ -14,26 +14,41 @@ import py_trees import carla -from agents.navigation.global_route_planner import GlobalRoutePlanner from srunner.scenariomanager.carla_data_provider import CarlaDataProvider -from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ActorFlow, TrafficLightFreezer -from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest -from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitEndIntersection +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ActorFlow, TrafficLightFreezer, ScenarioTimeout +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import WaitEndIntersection, DriveDistance from srunner.scenarios.basic_scenario import BasicScenario from srunner.tools.scenario_helper import (generate_target_waypoint, get_junction_topology, filter_junction_wp_direction, + get_same_dir_lanes, get_closest_traffic_light) -from srunner.tools.background_manager import Scenario9Manager +from srunner.tools.background_manager import HandleJunctionScenario -class SignalizedJunctionRightTurn(BasicScenario): +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default + + +class JunctionRightTurn(BasicScenario): """ Scenario where the vehicle is turning right at an intersection an has to avoid - colliding with a vehicle coming from its left + colliding with vehicles coming from its left """ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, @@ -43,39 +58,51 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals """ self._world = world self._map = CarlaDataProvider.get_map() - self._source_dist = 40 - self._sink_dist = 10 - self._source_dist_interval = [25, 50] - self._opposite_speed = 35 / 3.6 - self._green_light_delay = 5 # Wait before the ego's lane traffic light turns green - self._direction = 'left' - self._route_planner = GlobalRoutePlanner(self._map, 2.0) self.timeout = timeout - super(SignalizedJunctionRightTurn, self).__init__("SignalizedJunctionRightTurn", - ego_vehicles, - config, - world, - debug_mode, - criteria_enable=criteria_enable) + + self._direction = 'left' + + self._green_light_delay = 5 # Wait before the ego's lane traffic light turns green + self._flow_tl_dict = {} + self._init_tl_dict = {} + + self._end_distance = 10 # Distance after the junction before the scenario ends + + self._flow_speed = get_value_parameter(config, 'flow_speed', float, 20) + self._source_dist_interval = get_interval_parameter(config, 'source_dist_interval', float, [25, 50]) + self._scenario_timeout = 240 + + # The faster the flow, the further they are spawned, leaving time to react to them + self._source_dist = 5 * self._flow_speed + self._sink_dist = 3 * self._flow_speed + + super().__init__("JunctionRightTurn", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) def _initialize_actors(self, config): """ Custom initialization """ ego_location = config.trigger_points[0].location - ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) + self._ego_wp = CarlaDataProvider.get_map().get_waypoint(ego_location) # Get the junction - starting_wp = ego_wp + starting_wp = self._ego_wp + ego_junction_dist = 0 while not starting_wp.is_junction: starting_wps = starting_wp.next(1.0) if len(starting_wps) == 0: - raise ValueError("Failed to find junction as a waypoint with no next was detected") + raise ValueError("Failed to find a junction") starting_wp = starting_wps[0] - junction = starting_wp.get_junction() + ego_junction_dist += 1 + self._junction = starting_wp.get_junction() # Get the source entry lane wp - entry_wps, _ = get_junction_topology(junction) + entry_wps, _ = get_junction_topology(self._junction) source_entry_wps = filter_junction_wp_direction(starting_wp, entry_wps, self._direction) if not source_entry_wps: raise ValueError("Trying to find a lane in the {} direction but none was found".format(self._direction)) @@ -90,15 +117,15 @@ def _initialize_actors(self, config): # Get the source transform source_wp = source_entry_wp - accum_dist = 0 - while accum_dist < self._source_dist: + source_junction_dist = 0 + while source_junction_dist < self._source_dist: source_wps = source_wp.previous(5) if len(source_wps) == 0: - raise ValueError("Failed to find a source location as a waypoint with no previous was detected") + raise ValueError("Failed to find a source location") if source_wps[0].is_junction: break source_wp = source_wps[0] - accum_dist += 5 + source_junction_dist += 5 self._source_wp = source_wp source_transform = self._source_wp.transform @@ -107,20 +134,53 @@ def _initialize_actors(self, config): sink_exit_wp = generate_target_waypoint(self._map.get_waypoint(source_transform.location), 0) sink_wps = sink_exit_wp.next(self._sink_dist) if len(sink_wps) == 0: - raise ValueError("Failed to find a sink location as a waypoint with no next was detected") + raise ValueError("Failed to find a sink location") self._sink_wp = sink_wps[0] - # Get the relevant traffic lights - tls = self._world.get_traffic_lights_in_junction(junction.id) - ego_tl = get_closest_traffic_light(ego_wp, tls) - source_tl = get_closest_traffic_light(source_wps[0], tls) - self._flow_tl_dict = {} - self._init_tl_dict = {} + def _create_behavior(self): + raise NotImplementedError("Found missing behavior") + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors upon deletion + """ + self.remove_all_actors() + + +class SignalizedJunctionRightTurn(JunctionRightTurn): + """ + Signalized version of JunctionRightTurn + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=80): + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + super()._initialize_actors(config) + + tls = self._world.get_traffic_lights_in_junction(self._junction.id) + ego_tl = get_closest_traffic_light(self._ego_wp, tls) + source_tl = get_closest_traffic_light(self._source_wp, tls) + for tl in tls: - if tl == ego_tl: + if tl.id == ego_tl.id: self._flow_tl_dict[tl] = carla.TrafficLightState.Green self._init_tl_dict[tl] = carla.TrafficLightState.Red - elif tl == source_tl: + elif tl.id == source_tl.id: self._flow_tl_dict[tl] = carla.TrafficLightState.Green self._init_tl_dict[tl] = carla.TrafficLightState.Green else: @@ -132,32 +192,69 @@ def _create_behavior(self): Hero vehicle is turning right in an urban area, at a signalized intersection, while other actor coming straight from the left. The ego has to avoid colliding with it """ + + sequence = py_trees.composites.Sequence(name="JunctionRightTurn") + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=get_same_dir_lanes(self._source_wp), + remove_exits=[], + stop_entries=False, + extend_road_exit=self._sink_dist + 20 + )) + root = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) - root.add_child(WaitEndIntersection(self.ego_vehicles[0])) + end_condition = py_trees.composites.Sequence() + end_condition.add_child(WaitEndIntersection(self.ego_vehicles[0])) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + root.add_child(end_condition) root.add_child(ActorFlow( - self._source_wp, self._sink_wp, self._source_dist_interval, 2, self._opposite_speed)) + self._source_wp, self._sink_wp, self._source_dist_interval, 2, self._flow_speed, initial_actors=True)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) tl_freezer_sequence = py_trees.composites.Sequence("Traffic Light Behavior") tl_freezer_sequence.add_child(TrafficLightFreezer(self._init_tl_dict, duration=self._green_light_delay)) tl_freezer_sequence.add_child(TrafficLightFreezer(self._flow_tl_dict)) root.add_child(tl_freezer_sequence) - sequence = py_trees.composites.Sequence("Sequence") - if CarlaDataProvider.get_ego_vehicle_route(): - sequence.add_child(Scenario9Manager(self._direction)) sequence.add_child(root) - return sequence - def _create_test_criteria(self): - """ - A list of all test criteria will be created that is later used - in parallel behavior tree. - """ - return [CollisionTest(self.ego_vehicles[0])] - def __del__(self): +class NonSignalizedJunctionRightTurn(JunctionRightTurn): + """ + Non signalized version of JunctionRightTurn + """ + + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=80): + super().__init__(world, ego_vehicles, config, randomize, debug_mode, criteria_enable, timeout) + + def _create_behavior(self): """ - Remove all actors upon deletion + Hero vehicle is turning right in an urban area, at a signalized intersection, + while other actor coming straight from the left. The ego has to avoid colliding with it """ - self.remove_all_actors() + sequence = py_trees.composites.Sequence(name="JunctionRightTurn") + if self.route_mode: + sequence.add_child(HandleJunctionScenario( + clear_junction=True, + clear_ego_entry=True, + remove_entries=get_same_dir_lanes(self._source_wp), + remove_exits=[], + stop_entries=True, + extend_road_exit=self._sink_dist + 20 + )) + + root = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition = py_trees.composites.Sequence() + end_condition.add_child(WaitEndIntersection(self.ego_vehicles[0])) + end_condition.add_child(DriveDistance(self.ego_vehicles[0], self._end_distance)) + root.add_child(end_condition) + root.add_child(ActorFlow( + self._source_wp, self._sink_wp, self._source_dist_interval, 2, self._flow_speed, initial_actors=True)) + root.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + + sequence.add_child(root) + return sequence diff --git a/srunner/scenarios/vehicle_opens_door.py b/srunner/scenarios/vehicle_opens_door.py new file mode 100644 index 000000000..1f226e3b3 --- /dev/null +++ b/srunner/scenarios/vehicle_opens_door.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenarios in which another (opposite) vehicle 'illegally' takes +priority, e.g. by running a red traffic light. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, ScenarioTimeoutTest + +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorDestroy, + OpenVehicleDoor, + SwitchWrongDirectionTest, + ScenarioTimeout, + Idle, + OppositeActorFlow) +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToLocation, + InTimeToArrivalToLocation, + DriveDistance, + WaitUntilInFrontPosition) +from srunner.scenarios.basic_scenario import BasicScenario + +from srunner.tools.background_manager import LeaveSpaceInFront, ChangeOppositeBehavior, StopBackVehicles, StartBackVehicles + + +def get_value_parameter(config, name, p_type, default): + if name in config.other_parameters: + return p_type(config.other_parameters[name]['value']) + else: + return default + + +def get_interval_parameter(config, name, p_type, default): + if name in config.other_parameters: + return [ + p_type(config.other_parameters[name]['from']), + p_type(config.other_parameters[name]['to']) + ] + else: + return default + + +class VehicleOpensDoorTwoWays(BasicScenario): + """ + This class holds everything required for a scenario in which another vehicle parked at the side lane + opens the door, forcing the ego to lane change, invading the opposite lane + """ + def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + + self.timeout = timeout + self._min_trigger_dist = 10 + self._reaction_time = 3.0 + + self._opposite_wait_duration = 5 + self._end_distance = 50 + + self._parked_distance = get_value_parameter(config, 'distance', float, 50) + self._direction = get_value_parameter(config, 'direction', str, 'right') + if self._direction not in ('left', 'right'): + raise ValueError(f"'direction' must be either 'right' or 'left' but {self._direction} was given") + + self._max_speed = get_value_parameter(config, 'speed', float, 60) + self._scenario_timeout = 240 + + self._opposite_interval = get_interval_parameter(config, 'frequency', float, [20, 100]) + + super().__init__("VehicleOpensDoorTwoWays", ego_vehicles, config, world, debug_mode, criteria_enable=criteria_enable) + + def _get_displaced_location(self, actor, wp): + """ + Calculates the transforming such that the actor is at the sidemost part of the lane + """ + # Move the actor to the edge of the lane, or the open door might not reach the ego vehicle + displacement = (wp.lane_width - actor.bounding_box.extent.y) / 4 + displacement_vector = wp.transform.get_right_vector() + if self._direction == 'right': + displacement_vector *= -1 + + new_location = wp.transform.location + carla.Location(x=displacement*displacement_vector.x, + y=displacement*displacement_vector.y, + z=displacement*displacement_vector.z) + new_location.z += 0.05 # Just in case, avoid collisions with the ground + return new_location + + def _move_waypoint_forward(self, wp, distance): + dist = 0 + next_wp = wp + while dist < distance: + next_wps = next_wp.next(1) + if not next_wps or next_wps[0].is_junction: + break + next_wp = next_wps[0] + dist += 1 + return next_wp + + def _initialize_actors(self, config): + """ + Creates a parked vehicle on the side of the road + """ + trigger_location = config.trigger_points[0].location + starting_wp = self._map.get_waypoint(trigger_location) + front_wps = starting_wp.next(self._parked_distance) + if len(front_wps) == 0: + raise ValueError("Couldn't find a spot to place the adversary vehicle") + elif len(front_wps) > 1: + print("WARNING: Found a diverging lane. Choosing one at random") + self._front_wp = front_wps[0] + + if self._direction == 'left': + self._parked_wp = self._front_wp.get_left_lane() + else: + self._parked_wp = self._front_wp.get_right_lane() + + if self._parked_wp is None: + raise ValueError("Couldn't find a spot to place the adversary vehicle") + + self.parking_slots.append(self._parked_wp.transform.location) + + self._parked_actor = CarlaDataProvider.request_new_actor( + "*vehicle.*", self._parked_wp.transform, attribute_filter={'has_dynamic_doors': True, 'base_type': 'car'}) + if not self._parked_actor: + raise ValueError("Couldn't spawn the parked vehicle") + self.other_actors.append(self._parked_actor) + + # And move it to the side + side_location = self._get_displaced_location(self._parked_actor, self._parked_wp) + self._parked_actor.set_location(side_location) + self._parked_actor.apply_control(carla.VehicleControl(hand_brake=True)) + + self._end_wp = self._move_waypoint_forward(self._front_wp, self._end_distance) + + def _create_behavior(self): + """ + Leave space in front, as the TM doesn't detect open doors, and change the opposite frequency + so that the ego can pass + """ + reference_wp = self._parked_wp.get_left_lane() + if not reference_wp: + raise ValueError("Couldnt find a left lane to spawn the opposite traffic") + + root = py_trees.composites.Sequence(name="VehicleOpensDoorTwoWays") + if self.route_mode: + total_dist = self._parked_distance + 20 + root.add_child(LeaveSpaceInFront(total_dist)) + + end_condition = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + end_condition.add_child(ScenarioTimeout(self._scenario_timeout, self.config.name)) + end_condition.add_child(WaitUntilInFrontPosition(self.ego_vehicles[0], self._end_wp.transform, False)) + + behavior = py_trees.composites.Sequence(name="Main Behavior") + + # Wait until ego is close to the adversary + collision_location = self._front_wp.transform.location + trigger_adversary = py_trees.composites.Parallel( + policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="TriggerOpenDoor") + trigger_adversary.add_child(InTimeToArrivalToLocation( + self.ego_vehicles[0], self._reaction_time, collision_location)) + trigger_adversary.add_child(InTriggerDistanceToLocation( + self.ego_vehicles[0], collision_location, self._min_trigger_dist)) + behavior.add_child(trigger_adversary) + + door = carla.VehicleDoor.FR if self._direction == 'left' else carla.VehicleDoor.FL + behavior.add_child(OpenVehicleDoor(self._parked_actor, door)) + behavior.add_child(StopBackVehicles()) + behavior.add_child(Idle(self._opposite_wait_duration)) + if self.route_mode: + behavior.add_child(SwitchWrongDirectionTest(False)) + behavior.add_child(ChangeOppositeBehavior(active=False)) + behavior.add_child(OppositeActorFlow(reference_wp, self.ego_vehicles[0], self._opposite_interval)) + + end_condition.add_child(behavior) + root.add_child(end_condition) + + if self.route_mode: + root.add_child(SwitchWrongDirectionTest(True)) + root.add_child(ChangeOppositeBehavior(active=True)) + for actor in self.other_actors: + root.add_child(ActorDestroy(actor)) + root.add_child(StartBackVehicles()) + + return root + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criteria = [ScenarioTimeoutTest(self.ego_vehicles[0], self.config.name)] + if not self.route_mode: + criteria.append(CollisionTest(self.ego_vehicles[0])) + return criteria + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/scenarios/yield_to_emergency_vehicle.py b/srunner/scenarios/yield_to_emergency_vehicle.py new file mode 100644 index 000000000..f77750e14 --- /dev/null +++ b/srunner/scenarios/yield_to_emergency_vehicle.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python + +# Copyright (c) 2018-2020 Intel Corporation +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + +""" +Scenario in which the ego has to yield its lane to emergency vehicle. +""" + +from __future__ import print_function + +import py_trees +import carla + +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider +from srunner.scenariomanager.scenarioatomics.atomic_behaviors import (ActorTransformSetter, + ActorDestroy, + Idle, + AdaptiveConstantVelocityAgentBehavior) +from srunner.scenariomanager.scenarioatomics.atomic_criteria import CollisionTest, YieldToEmergencyVehicleTest +from srunner.scenariomanager.scenarioatomics.atomic_trigger_conditions import (InTriggerDistanceToVehicle, + WaitUntilInFront, + DriveDistance) +from srunner.scenarios.basic_scenario import BasicScenario +from srunner.tools.background_manager import RemoveRoadLane, ReAddRoadLane + + +class YieldToEmergencyVehicle(BasicScenario): + """ + This class holds everything required for a scenario in which the ego has to yield its lane to emergency vehicle. + The background activity will be removed from the lane the emergency vehicle will pass through, + and will be recreated once the scenario is over. + + Should be on the highway which is long enough and has no junctions. + There should be at least two lanes on the highway. + """ + + def __init__(self, world, ego_vehicles, config, debug_mode=False, criteria_enable=True, + timeout=180): + """ + Setup all relevant parameters and create scenario + and instantiate scenario manager + """ + self._world = world + self._map = CarlaDataProvider.get_map() + self.timeout = timeout + self._ev_idle_time = 10 # seconds + + # km/h. How much the EV is expected to be faster than the EGO + self._speed_increment = 25 + + self._trigger_distance = 50 + + if 'distance' in config.other_parameters: + self._distance = float(config.other_parameters['distance']['value']) + else: + self._distance = 140 # m + + # Change some of the parameters to adapt its behavior. + # 1) ConstantVelocityAgent = infinite acceleration -> reduce the detection radius to pressure the ego + # 2) Always use the bb check to ensure the EV doesn't run over the ego when it is lane changing + # 3) Add more wps to improve BB detection + self._opt_dict = { + 'base_vehicle_threshold': 10, 'detection_speed_ratio': 0.15, 'use_bbs_detection': True, + 'base_min_distance': 1, 'distance_ratio': 0.2 + } + + self._trigger_location = config.trigger_points[0].location + self._reference_waypoint = self._map.get_waypoint(self._trigger_location) + + self._end_distance = 50 + + super().__init__("YieldToEmergencyVehicle", + ego_vehicles, + config, + world, + debug_mode, + criteria_enable=criteria_enable) + + def _initialize_actors(self, config): + """ + Custom initialization + """ + # Spawn emergency vehicle + ev_points = self._reference_waypoint.previous(self._distance) + if not ev_points: + raise ValueError("Couldn't find viable position for the emergency vehicle") + + self._ev_start_transform = ev_points[0].transform + + actor = CarlaDataProvider.request_new_actor( + "vehicle.*.*", self._ev_start_transform, attribute_filter={'special_type': 'emergency'}) + if actor is None: + raise Exception("Couldn't spawn the emergency vehicle") + + # Move the actor underground and remove its physics so that it doesn't fall + actor.set_simulate_physics(False) + new_location = actor.get_location() + new_location.z -= 500 + actor.set_location(new_location) + + # Turn on special lights + actor.set_light_state(carla.VehicleLightState( + carla.VehicleLightState.Special1 | carla.VehicleLightState.Special2)) + + self.other_actors.append(actor) + + def _create_behavior(self): + """ + Spawn the EV behind and wait for it to be close-by. After it has approached, + give the ego a certain amount of time to yield to it. + + Sequence: + - RemoveRoadLane + - ActorTransformSetter + - Parallel: + - AdaptiveConstantVelocityAgentBehavior + - Sequence: (End condition 1) + - InTriggerDistanceToVehicle: + - Idle + - Sequence: (End condition 2) + - WaitUntilInFront + - DriveDistance + - ReAddRoadLane + """ + sequence = py_trees.composites.Sequence(name="YieldToEmergencyVehicle") + + if self.route_mode: + sequence.add_child(RemoveRoadLane(self._reference_waypoint)) + + sequence.add_child(ActorTransformSetter(self.other_actors[0], self._ev_start_transform)) + + main_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) + + end_condition_1 = py_trees.composites.Sequence() + end_condition_1.add_child(InTriggerDistanceToVehicle( + self.ego_vehicles[0], self.other_actors[0], self._trigger_distance)) + end_condition_1.add_child(Idle(self._ev_idle_time)) + + end_condition_2 = py_trees.composites.Sequence() + end_condition_2.add_child(WaitUntilInFront(self.other_actors[0], self.ego_vehicles[0])) + end_condition_2.add_child(DriveDistance(self.other_actors[0], self._end_distance)) + + main_behavior.add_child(end_condition_1) + main_behavior.add_child(end_condition_2) + + main_behavior.add_child(AdaptiveConstantVelocityAgentBehavior( + self.other_actors[0], self.ego_vehicles[0], speed_increment=self._speed_increment, opt_dict=self._opt_dict)) + + sequence.add_child(main_behavior) + + sequence.add_child(ActorDestroy(self.other_actors[0])) + + if self.route_mode: + sequence.add_child(ReAddRoadLane(0)) + + return sequence + + def _create_test_criteria(self): + """ + A list of all test criteria will be created that is later used + in parallel behavior tree. + """ + criterias = [] + criterias.append(YieldToEmergencyVehicleTest(self.ego_vehicles[0], self.other_actors[0])) + if not self.route_mode: + criterias.append(CollisionTest(self.ego_vehicles[0])) + + return criterias + + def __del__(self): + """ + Remove all actors and traffic lights upon deletion + """ + self.remove_all_actors() diff --git a/srunner/tools/background_manager.py b/srunner/tools/background_manager.py index 5519a5af8..436ff4262 100644 --- a/srunner/tools/background_manager.py +++ b/srunner/tools/background_manager.py @@ -11,190 +11,276 @@ import py_trees from srunner.scenariomanager.scenarioatomics.atomic_behaviors import AtomicBehavior +from srunner.scenariomanager.timer import GameTime +from srunner.scenariomanager.carla_data_provider import CarlaDataProvider -class RoadBehaviorManager(AtomicBehavior): +class ChangeRoadBehavior(AtomicBehavior): """ Updates the blackboard to change the parameters of the road behavior. - None values imply that these values won't be changed + None values imply that these values won't be changed. Args: num_front_vehicles (int): Amount of vehicles in front of the ego. Can't be negative num_back_vehicles (int): Amount of vehicles behind it. Can't be negative - vehicle_dist (float): Minimum distance between the road vehicles. Must between 0 and 'spawn_dist' - spawn_dist (float): Minimum distance between spawned vehicles. Must be positive + switch_source (bool): (De)activatea the road sources. """ - def __init__(self, num_front_vehicles=None, num_back_vehicles=None, - vehicle_dist=None, spawn_dist=None, name="RoadBehaviorManager"): - self._num_front_vehicles = num_front_vehicles - self._num_back_vehicles = num_back_vehicles - self._vehicle_dist = vehicle_dist + def __init__(self, num_front_vehicles=None, num_back_vehicles=None, spawn_dist=None, extra_space=None, name="ChangeRoadBehavior"): + self._num_front = num_front_vehicles + self._num_back = num_back_vehicles self._spawn_dist = spawn_dist - super(RoadBehaviorManager, self).__init__(name) + self._extra_space = extra_space + super().__init__(name) def update(self): py_trees.blackboard.Blackboard().set( - "BA_RoadBehavior", - [self._num_front_vehicles, self._num_back_vehicles, self._vehicle_dist, self._spawn_dist], - overwrite=True + "BA_ChangeRoadBehavior", [self._num_front, self._num_back, self._spawn_dist, self._extra_space], overwrite=True ) return py_trees.common.Status.SUCCESS -class OppositeBehaviorManager(AtomicBehavior): +class ChangeOppositeBehavior(AtomicBehavior): """ Updates the blackboard to change the parameters of the opposite road behavior. None values imply that these values won't be changed Args: source_dist (float): Distance between the opposite sources and the ego vehicle. Must be positive - vehicle_dist (float) Minimum distance between the opposite vehicles. Must between 0 and 'spawn_dist' - spawn_dist (float): Minimum distance between spawned vehicles. Must be positive max_actors (int): Max amount of concurrent alive actors spawned by the same source. Can't be negative """ - def __init__(self, source_dist=None, vehicle_dist=None, spawn_dist=None, - max_actors=None, name="OppositeBehaviorManager"): + def __init__(self, source_dist=None, spawn_dist=None, active=None, name="ChangeOppositeBehavior"): self._source_dist = source_dist - self._vehicle_dist = vehicle_dist self._spawn_dist = spawn_dist - self._max_actors = max_actors - super(OppositeBehaviorManager, self).__init__(name) + self._active = active + super().__init__(name) def update(self): py_trees.blackboard.Blackboard().set( - "BA_OppositeBehavior", - [self._source_dist, self._vehicle_dist, self._spawn_dist, self._max_actors], - overwrite=True + "BA_ChangeOppositeBehavior", [self._source_dist, self._spawn_dist, self._active], overwrite=True ) return py_trees.common.Status.SUCCESS -class JunctionBehaviorManager(AtomicBehavior): +class ChangeJunctionBehavior(AtomicBehavior): """ Updates the blackboard to change the parameters of the junction behavior. None values imply that these values won't be changed Args: source_dist (float): Distance between the junctiob sources and the junction entry. Must be positive - vehicle_dist (float) Minimum distance between the junction vehicles. Must between 0 and 'spawn_dist' - spawn_dist (float): Minimum distance between spawned vehicles. Must be positive max_actors (int): Max amount of concurrent alive actors spawned by the same source. Can't be negative - """ - def __init__(self, source_dist=None, vehicle_dist=None, spawn_dist=None, - max_actors=None, name="JunctionBehaviorManager"): + def __init__(self, source_dist=None, spawn_dist=None, max_actors=None, source_perc=None, name="ChangeJunctionBehavior"): self._source_dist = source_dist - self._vehicle_dist = vehicle_dist self._spawn_dist = spawn_dist self._max_actors = max_actors - super(JunctionBehaviorManager, self).__init__(name) + self._perc = source_perc + super().__init__(name) def update(self): py_trees.blackboard.Blackboard().set( - "BA_JunctionBehavior", - [self._source_dist, self._vehicle_dist, self._spawn_dist, self._max_actors], - overwrite=True + "BA_ChangeJunctionBehavior", [self._source_dist, self._spawn_dist, self._max_actors, self._perc], overwrite=True ) return py_trees.common.Status.SUCCESS -class Scenario2Manager(AtomicBehavior): +class SetMaxSpeed(AtomicBehavior): + """ + Updates the blackboard to tell the background activity that its behavior is restriced to a maximum speed + """ + + def __init__(self, max_speed, name="SetMaxSpeed"): + self._max_speed = max_speed + super().__init__(name) + + def update(self): + py_trees.blackboard.Blackboard().set("BA_SetMaxSpeed", self._max_speed, overwrite=True) + return py_trees.common.Status.SUCCESS + + +class StopFrontVehicles(AtomicBehavior): """ - Updates the blackboard to tell the background activity that a Scenario2 has to be triggered. + Updates the blackboard to tell the background activity that a HardBreak scenario has to be triggered. 'stop_duration' is the amount of time, in seconds, the vehicles will be stopped """ - def __init__(self, stop_duration=10, name="Scenario2Manager"): - self._stop_duration = stop_duration - super(Scenario2Manager, self).__init__(name) + def __init__(self, name="StopFrontVehicles"): + super().__init__(name) def update(self): - py_trees.blackboard.Blackboard().set("BA_Scenario2", self._stop_duration, overwrite=True) + py_trees.blackboard.Blackboard().set("BA_StopFrontVehicles", True, overwrite=True) return py_trees.common.Status.SUCCESS -class Scenario4Manager(AtomicBehavior): +class StartFrontVehicles(AtomicBehavior): """ - Updates the blackboard to tell the background activity that a Scenario4 has been triggered. - 'crossing_dist' is the distance between the crossing actor and the junction + Updates the blackboard to tell the background activity that a HardBreak scenario has to be triggered. + 'stop_duration' is the amount of time, in seconds, the vehicles will be stopped """ - def __init__(self, crossing_dist=10, name="Scenario4Manager"): - self._crossing_dist = crossing_dist - super(Scenario4Manager, self).__init__(name) + def __init__(self, name="StartFrontVehicles"): + super().__init__(name) + + def update(self): + py_trees.blackboard.Blackboard().set("BA_StartFrontVehicles", True, overwrite=True) + return py_trees.common.Status.SUCCESS + + +class StopBackVehicles(AtomicBehavior): + """ + Updates the blackboard to tell the background activity to stop the vehicles behind the ego as to + not interfere with the scenarios. This only works at roads, not junctions. + """ + def __init__(self, name="StopBackVehicles"): + super().__init__(name) def update(self): """Updates the blackboard and succeds""" - py_trees.blackboard.Blackboard().set("BA_Scenario4", self._crossing_dist, overwrite=True) + py_trees.blackboard.Blackboard().set("BA_StopBackVehicles", True, overwrite=True) return py_trees.common.Status.SUCCESS -class Scenario7Manager(AtomicBehavior): +class StartBackVehicles(AtomicBehavior): """ - Updates the blackboard to tell the background activity that a Scenario7 has been triggered - 'entry_direction' is the direction from which the incoming traffic enters the junction. It should be - something like 'left', 'right' or 'opposite' + Updates the blackboard to tell the background activity to restart the vehicles behind the ego. """ + def __init__(self, name="StartBackVehicles"): + super().__init__(name) - def __init__(self, entry_direction, name="Scenario7Manager"): - self._entry_direction = entry_direction - super(Scenario7Manager, self).__init__(name) + def update(self): + """Updates the blackboard and succeds""" + py_trees.blackboard.Blackboard().set("BA_StartBackVehicles", True, overwrite=True) + return py_trees.common.Status.SUCCESS + + +class LeaveSpaceInFront(AtomicBehavior): + """ + Updates the blackboard to tell the background activity that the ego needs more space in front. + This only works at roads, not junctions. + """ + def __init__(self, space, name="LeaveSpaceInFront"): + self._space = space + super().__init__(name) def update(self): """Updates the blackboard and succeds""" - py_trees.blackboard.Blackboard().set("BA_Scenario7", self._entry_direction, overwrite=True) + py_trees.blackboard.Blackboard().set("BA_LeaveSpaceInFront", [self._space], overwrite=True) return py_trees.common.Status.SUCCESS -class Scenario8Manager(AtomicBehavior): +class SwitchRouteSources(AtomicBehavior): """ - Updates the blackboard to tell the background activity that a Scenario8 has been triggered - 'entry_direction' is the direction from which the incoming traffic enters the junction. It should be - something like 'left', 'right' or 'opposite' + Updates the blackboard to tell the background activity to (de)activate all route sources """ + def __init__(self, enabled=True, name="SwitchRouteSources"): + self._enabled = enabled + super().__init__(name) - def __init__(self, entry_direction, name="Scenario8Manager"): - self._entry_direction = entry_direction - super(Scenario8Manager, self).__init__(name) + def update(self): + """Updates the blackboard and succeds""" + py_trees.blackboard.Blackboard().set("BA_SwitchRouteSources", self._enabled, overwrite=True) + return py_trees.common.Status.SUCCESS + + +class RemoveRoadLane(AtomicBehavior): + """ + Updates the blackboard to tell the background activity to remove its actors from the given lane + and stop generating new ones on this lane, or recover from stopping. + + Args: + lane_wp (carla.Waypoint): A carla.Waypoint + active (bool) + """ + def __init__(self, lane_wp, name="RemoveRoadLane"): + self._lane_wp = lane_wp + super().__init__(name) def update(self): """Updates the blackboard and succeds""" - py_trees.blackboard.Blackboard().set("BA_Scenario8", self._entry_direction, overwrite=True) + py_trees.blackboard.Blackboard().set("BA_RemoveRoadLane", self._lane_wp, overwrite=True) return py_trees.common.Status.SUCCESS -class Scenario9Manager(AtomicBehavior): +class ReAddRoadLane(AtomicBehavior): """ - Updates the blackboard to tell the background activity that a Scenario9 has been triggered - 'entry_direction' is the direction from which the incoming traffic enters the junction. It should be - something like 'left', 'right' or 'opposite' + Updates the blackboard to tell the background activity to readd the ego road lane. + + Args: + offset: 0 to readd the ego lane, 1 for the right side lane, -1 for the left... + active (bool) """ + def __init__(self, offset, name="BA_ReAddRoadLane"): + self._offset = offset + super().__init__(name) + + def update(self): + """Updates the blackboard and succeds""" + py_trees.blackboard.Blackboard().set("BA_ReAddRoadLane", self._offset, overwrite=True) + return py_trees.common.Status.SUCCESS + - def __init__(self, entry_direction, name="Scenario9Manager"): - self._entry_direction = entry_direction - super(Scenario9Manager, self).__init__(name) +class LeaveSpaceInFront(AtomicBehavior): + """ + Updates the blackboard to tell the background activity that the ego needs more space in front. + This only works at roads, not junctions. + """ + def __init__(self, space, name="LeaveSpaceInFront"): + self._space = space + super().__init__(name) def update(self): """Updates the blackboard and succeds""" - py_trees.blackboard.Blackboard().set("BA_Scenario9", self._entry_direction, overwrite=True) + py_trees.blackboard.Blackboard().set("BA_LeaveSpaceInFront", self._space, overwrite=True) return py_trees.common.Status.SUCCESS -class Scenario10Manager(AtomicBehavior): +class LeaveCrossingSpace(AtomicBehavior): """ - Updates the blackboard to tell the background activity that a Scenario10 has been triggered - 'entry_direction' is the direction from which the incoming traffic enters the junction. It should be - something like 'left', 'right' or 'opposite' + Updates the blackboard to tell the background activity that the ego needs more space in front. + This only works at roads, not junctions. """ + def __init__(self, collision_wp, name="LeaveCrossingSpace"): + self._collision_wp = collision_wp + super().__init__(name) - def __init__(self, entry_direction, name="Scenario10Manager"): - self._entry_direction = entry_direction - super(Scenario10Manager, self).__init__(name) + def update(self): + """Updates the blackboard and succeds""" + py_trees.blackboard.Blackboard().set("BA_LeaveCrossingSpace", self._collision_wp, overwrite=True) + return py_trees.common.Status.SUCCESS + +class HandleJunctionScenario(AtomicBehavior): + """ + Updates the blackboard to tell the background activity to adapt to a junction scenario + + Args: + clear_junction (bool): Remove all actors inside the junction, and all that enter it afterwards + clear_ego_entry (bool): Remove all actors part of the ego road to ensure a smooth entry of the ego to the junction. + remove_entries (list): list of waypoint representing a junction entry that needs to be removed + remove_exits (list): list of waypoint representing a junction exit that needs to be removed + stop_entries (bool): Stops all the junction entries + extend_road_exit (float): Moves the road junction actors forward to leave more space for the scenario. + It also deactivates the road sources. + active (bool) + """ + def __init__(self, clear_junction=True, clear_ego_entry=True, remove_entries=[], + remove_exits=[], stop_entries=True, extend_road_exit=0, + name="HandleJunctionScenario"): + self._clear_junction = clear_junction + self._clear_ego_entry = clear_ego_entry + self._remove_entries = remove_entries + self._remove_exits = remove_exits + self._stop_entries = stop_entries + self._extend_road_exit = extend_road_exit + super().__init__(name) def update(self): """Updates the blackboard and succeds""" - py_trees.blackboard.Blackboard().set("BA_Scenario10", self._entry_direction, overwrite=True) + py_trees.blackboard.Blackboard().set( + "BA_HandleJunctionScenario", + [self._clear_junction, self._clear_ego_entry, self._remove_entries, + self._remove_exits, self._stop_entries, self._extend_road_exit], + overwrite=True) return py_trees.common.Status.SUCCESS diff --git a/srunner/tools/openscenario_parser.py b/srunner/tools/openscenario_parser.py index d28ff6d7f..a7c8e05ea 100644 --- a/srunner/tools/openscenario_parser.py +++ b/srunner/tools/openscenario_parser.py @@ -266,7 +266,7 @@ def get_traffic_light_from_osc_name(name): # Given by id if name.startswith("id="): tl_id = int(name[3:]) - for carla_tl in CarlaDataProvider.get_world().get_actors().filter('traffic.traffic_light'): + for carla_tl in CarlaDataProvider.get_all_actors().filter('traffic.traffic_light'): if carla_tl.id == tl_id: traffic_light = carla_tl break @@ -274,7 +274,7 @@ def get_traffic_light_from_osc_name(name): elif name.startswith("pos="): tl_pos = name[4:] pos = tl_pos.split(",") - for carla_tl in CarlaDataProvider.get_world().get_actors().filter('traffic.traffic_light'): + for carla_tl in CCarlaDataProvider.get_all_actors().filter('traffic.traffic_light'): carla_tl_location = carla_tl.get_transform().location distance = carla_tl_location.distance(carla.Location(float(pos[0]), float(pos[1]), @@ -671,7 +671,7 @@ def convert_position_to_transform(position, actor_list=None): obj_actor = actor actor_transform = actor.transform else: - for actor in CarlaDataProvider.get_world().get_actors(): + for actor in CarlaDataProvider.get_all_actors(): if 'role_name' in actor.attributes and actor.attributes['role_name'] == obj: obj_actor = actor actor_transform = obj_actor.get_transform() diff --git a/srunner/tools/route_manipulation.py b/srunner/tools/route_manipulation.py index b7207c836..c657c1b0f 100644 --- a/srunner/tools/route_manipulation.py +++ b/srunner/tools/route_manipulation.py @@ -134,7 +134,7 @@ def interpolate_trajectory(waypoints_trajectory, hop_resolution=1.0): """ Given some raw keypoints interpolate a full dense trajectory to be used by the user. returns the full interpolated route both in GPS coordinates and also in its original form. - + Args: - waypoints_trajectory: the current coarse trajectory - hop_resolution: distance between the trajectory's waypoints @@ -142,15 +142,19 @@ def interpolate_trajectory(waypoints_trajectory, hop_resolution=1.0): grp = GlobalRoutePlanner(CarlaDataProvider.get_map(), hop_resolution) # Obtain route plan + lat_ref, lon_ref = _get_latlon_ref(CarlaDataProvider.get_world()) + route = [] + gps_route = [] + for i in range(len(waypoints_trajectory) - 1): waypoint = waypoints_trajectory[i] waypoint_next = waypoints_trajectory[i + 1] interpolated_trace = grp.trace_route(waypoint, waypoint_next) - for wp_tuple in interpolated_trace: - route.append((wp_tuple[0].transform, wp_tuple[1])) - - lat_ref, lon_ref = _get_latlon_ref(CarlaDataProvider.get_world()) + for wp, connection in interpolated_trace: + route.append((wp.transform, connection)) + gps_coord = _location_to_gps(lat_ref, lon_ref, wp.transform.location) + gps_route.append((gps_coord, connection)) - return location_route_to_gps(route, lat_ref, lon_ref), route + return gps_route, route diff --git a/srunner/tools/route_parser.py b/srunner/tools/route_parser.py index c0e3f980b..89f1995f6 100644 --- a/srunner/tools/route_parser.py +++ b/srunner/tools/route_parser.py @@ -14,10 +14,27 @@ import carla from agents.navigation.local_planner import RoadOption from srunner.scenarioconfigs.route_scenario_configuration import RouteScenarioConfiguration +from srunner.scenarioconfigs.scenario_configuration import ScenarioConfiguration, ActorConfigurationData -# TODO check this threshold, it could be a bit larger but not so large that we cluster scenarios. -TRIGGER_THRESHOLD = 2.0 # Threshold to say if a trigger position is new or repeated, works for matching positions -TRIGGER_ANGLE_THRESHOLD = 10 # Threshold to say if two angles can be considering matching when matching transforms. +# Threshold to say if a scenarios trigger position is part of the route +DIST_THRESHOLD = 2.0 +ANGLE_THRESHOLD = 10 + + +def convert_elem_to_transform(elem): + """Convert an ElementTree.Element to a CARLA transform""" + return carla.Transform( + carla.Location( + float(elem.attrib.get('x')), + float(elem.attrib.get('y')), + float(elem.attrib.get('z')) + ), + carla.Rotation( + roll=0.0, + pitch=0.0, + yaw=float(elem.attrib.get('yaw')) + ) + ) class RouteParser(object): @@ -27,295 +44,100 @@ class RouteParser(object): """ @staticmethod - def parse_annotations_file(annotation_filename): - """ - Return the annotations of which positions where the scenarios are going to happen. - :param annotation_filename: the filename for the anotations file - :return: - """ - - with open(annotation_filename, 'r', encoding='utf-8') as f: - annotation_dict = json.loads(f.read()) - - final_dict = {} - - for town_dict in annotation_dict['available_scenarios']: - final_dict.update(town_dict) - - return final_dict # the file has a current maps name that is an one element vec - - @staticmethod - def parse_routes_file(route_filename, scenario_file, single_route=None): + def parse_routes_file(route_filename, single_route_id=''): """ - Returns a list of route elements. + Returns a list of route configuration elements. :param route_filename: the path to a set of routes. :param single_route: If set, only this route shall be returned :return: List of dicts containing the waypoints, id and town of the routes """ - list_route_descriptions = [] + route_configs = [] tree = ET.parse(route_filename) for route in tree.iter("route"): route_id = route.attrib['id'] - if single_route and route_id != single_route: + if single_route_id and route_id != single_route_id: continue - new_config = RouteScenarioConfiguration() - new_config.town = route.attrib['town'] - new_config.name = "RouteScenario_{}".format(route_id) - new_config.weather = RouteParser.parse_weather(route) - new_config.scenario_file = scenario_file - - waypoint_list = [] # the list of waypoints that can be found on this route - for waypoint in route.iter('waypoint'): - waypoint_list.append(carla.Location(x=float(waypoint.attrib['x']), - y=float(waypoint.attrib['y']), - z=float(waypoint.attrib['z']))) + route_config = RouteScenarioConfiguration() + route_config.town = route.attrib['town'] + route_config.name = "RouteScenario_{}".format(route_id) + route_config.weather = RouteParser.parse_weather(route) + + # The list of carla.Location that serve as keypoints on this route + positions = [] + for position in route.find('waypoints').iter('position'): + positions.append(carla.Location(x=float(position.attrib['x']), + y=float(position.attrib['y']), + z=float(position.attrib['z']))) + route_config.keypoints = positions + + # The list of ScenarioConfigurations that store the scenario's data + scenario_configs = [] + for scenario in route.find('scenarios').iter('scenario'): + scenario_config = ScenarioConfiguration() + scenario_config.name = scenario.attrib.get('name') + scenario_config.type = scenario.attrib.get('type') + + for elem in scenario.getchildren(): + if elem.tag == 'trigger_point': + scenario_config.trigger_points.append(convert_elem_to_transform(elem)) + elif elem.tag == 'other_actor': + scenario_config.other_actors.append(ActorConfigurationData.parse_from_node(elem, 'scenario')) + else: + scenario_config.other_parameters[elem.tag] = elem.attrib - new_config.trajectory = waypoint_list + scenario_configs.append(scenario_config) + route_config.scenario_configs = scenario_configs - list_route_descriptions.append(new_config) + route_configs.append(route_config) - return list_route_descriptions + return route_configs @staticmethod def parse_weather(route): """ - Returns a carla.WeatherParameters with the corresponding weather for that route. If the route - has no weather attribute, the default one is triggered. - """ - - route_weather = route.find("weather") - if route_weather is None: - - weather = carla.WeatherParameters(sun_altitude_angle=70) - - else: - weather = carla.WeatherParameters() - for weather_attrib in route.iter("weather"): - - if 'cloudiness' in weather_attrib.attrib: - weather.cloudiness = float(weather_attrib.attrib['cloudiness']) - if 'precipitation' in weather_attrib.attrib: - weather.precipitation = float(weather_attrib.attrib['precipitation']) - if 'precipitation_deposits' in weather_attrib.attrib: - weather.precipitation_deposits = float(weather_attrib.attrib['precipitation_deposits']) - if 'wind_intensity' in weather_attrib.attrib: - weather.wind_intensity = float(weather_attrib.attrib['wind_intensity']) - if 'sun_azimuth_angle' in weather_attrib.attrib: - weather.sun_azimuth_angle = float(weather_attrib.attrib['sun_azimuth_angle']) - if 'sun_altitude_angle' in weather_attrib.attrib: - weather.sun_altitude_angle = float(weather_attrib.attrib['sun_altitude_angle']) - if 'wetness' in weather_attrib.attrib: - weather.wetness = float(weather_attrib.attrib['wetness']) - if 'fog_distance' in weather_attrib.attrib: - weather.fog_distance = float(weather_attrib.attrib['fog_distance']) - if 'fog_density' in weather_attrib.attrib: - weather.fog_density = float(weather_attrib.attrib['fog_density']) - - return weather - - @staticmethod - def check_trigger_position(new_trigger, existing_triggers): - """ - Check if this trigger position already exists or if it is a new one. - :param new_trigger: - :param existing_triggers: - :return: - """ - - for trigger_id in existing_triggers.keys(): - trigger = existing_triggers[trigger_id] - dx = trigger['x'] - new_trigger['x'] - dy = trigger['y'] - new_trigger['y'] - distance = math.sqrt(dx * dx + dy * dy) - - dyaw = (trigger['yaw'] - new_trigger['yaw']) % 360 - if distance < TRIGGER_THRESHOLD \ - and (dyaw < TRIGGER_ANGLE_THRESHOLD or dyaw > (360 - TRIGGER_ANGLE_THRESHOLD)): - return trigger_id - - return None - - @staticmethod - def convert_waypoint_float(waypoint): - """ - Convert waypoint values to float + Parses all the weather information as a list of [position, carla.WeatherParameters], + where the position represents a % of the route. """ - waypoint['x'] = float(waypoint['x']) - waypoint['y'] = float(waypoint['y']) - waypoint['z'] = float(waypoint['z']) - waypoint['yaw'] = float(waypoint['yaw']) + weathers = [] - @staticmethod - def match_world_location_to_route(world_location, route_description): - """ - We match this location to a given route. - world_location: - route_description: - """ - def match_waypoints(waypoint1, wtransform): - """ - Check if waypoint1 and wtransform are similar - """ - dx = float(waypoint1['x']) - wtransform.location.x - dy = float(waypoint1['y']) - wtransform.location.y - dz = float(waypoint1['z']) - wtransform.location.z - dpos = math.sqrt(dx * dx + dy * dy + dz * dz) + weathers_elem = route.find("weathers") + if weathers_elem is None: + return [[0, carla.WeatherParameters(sun_altitude_angle=70, cloudiness=50)]] - dyaw = (float(waypoint1['yaw']) - wtransform.rotation.yaw) % 360 + for weather_elem in weathers_elem.iter('weather'): + route_percentage = float(weather_elem.attrib['route_percentage']) - return dpos < TRIGGER_THRESHOLD \ - and (dyaw < TRIGGER_ANGLE_THRESHOLD or dyaw > (360 - TRIGGER_ANGLE_THRESHOLD)) + weather = carla.WeatherParameters(sun_altitude_angle=70, cloudiness=50) # Base weather + for weather_attrib in weather_elem.attrib: + if hasattr(weather, weather_attrib): + setattr(weather, weather_attrib, float(weather_elem.attrib[weather_attrib])) + elif weather_attrib != 'route_percentage': + print(f"WARNING: Ignoring '{weather_attrib}', as it isn't a weather parameter") - match_position = 0 - # TODO this function can be optimized to run on Log(N) time - for route_waypoint in route_description: - if match_waypoints(world_location, route_waypoint[0]): - return match_position - match_position += 1 + weathers.append([route_percentage, weather]) - return None + weathers.sort(key=lambda x: x[0]) + return weathers @staticmethod - def get_scenario_type(scenario, match_position, trajectory): + def is_scenario_at_route(trigger_transform, route): """ - Some scenarios have different types depending on the route. - :param scenario: the scenario name - :param match_position: the matching position for the scenarion - :param trajectory: the route trajectory the ego is following - :return: tag representing this subtype - - Also used to check which are not viable (Such as an scenario - that triggers when turning but the route doesnt') - WARNING: These tags are used at: - - VehicleTurningRoute - - SignalJunctionCrossingRoute - and changes to these tags will affect them + Check if the scenario is affecting the route. + This is true if the trigger position is very close to any route point """ + def is_trigger_close(trigger_transform, route_transform): + """Check if the two transforms are similar""" + dist = trigger_transform.location.distance(route_transform.location) + angle_dist = (trigger_transform.rotation.yaw - route_transform.rotation.yaw) % 360 - def check_this_waypoint(tuple_wp_turn): - """ - Decides whether or not the waypoint will define the scenario behavior - """ - if RoadOption.LANEFOLLOW == tuple_wp_turn[1]: - return False - elif RoadOption.CHANGELANELEFT == tuple_wp_turn[1]: - return False - elif RoadOption.CHANGELANERIGHT == tuple_wp_turn[1]: - return False - return True - - # Unused tag for the rest of scenarios, - # can't be None as they are still valid scenarios - subtype = 'valid' - - if scenario == 'Scenario4': - for tuple_wp_turn in trajectory[match_position:]: - if check_this_waypoint(tuple_wp_turn): - if RoadOption.LEFT == tuple_wp_turn[1]: - subtype = 'S4left' - elif RoadOption.RIGHT == tuple_wp_turn[1]: - subtype = 'S4right' - else: - subtype = None - break # Avoid checking all of them - subtype = None - - if scenario == 'Scenario7': - for tuple_wp_turn in trajectory[match_position:]: - if check_this_waypoint(tuple_wp_turn): - if RoadOption.STRAIGHT == tuple_wp_turn[1]: - subtype = 'S7opposite' - else: - subtype = None - break # Avoid checking all of them - subtype = None - - if scenario == 'Scenario8': - for tuple_wp_turn in trajectory[match_position:]: - if check_this_waypoint(tuple_wp_turn): - if RoadOption.LEFT == tuple_wp_turn[1]: - subtype = 'S8left' - else: - subtype = None - break # Avoid checking all of them - subtype = None - - if scenario == 'Scenario9': - for tuple_wp_turn in trajectory[match_position:]: - if check_this_waypoint(tuple_wp_turn): - if RoadOption.RIGHT == tuple_wp_turn[1]: - subtype = 'S9right' - else: - subtype = None - break # Avoid checking all of them - subtype = None - - return subtype - - @staticmethod - def scan_route_for_scenarios(route_name, trajectory, world_annotations): - """ - Just returns a plain list of possible scenarios that can happen in this route by matching - the locations from the scenario into the route description - - :return: A list of scenario definitions with their correspondent parameters - """ - - # the triggers dictionaries: - existent_triggers = {} - # We have a table of IDs and trigger positions associated - possible_scenarios = {} - - # Keep track of the trigger ids being added - latest_trigger_id = 0 - - for town_name in world_annotations.keys(): - if town_name != route_name: - continue - - scenarios = world_annotations[town_name] - for scenario in scenarios: # For each existent scenario - if "scenario_type" not in scenario: - break - scenario_name = scenario["scenario_type"] - for event in scenario["available_event_configurations"]: - waypoint = event['transform'] # trigger point of this scenario - RouteParser.convert_waypoint_float(waypoint) - # We match trigger point to the route, now we need to check if the route affects - match_position = RouteParser.match_world_location_to_route( - waypoint, trajectory) - if match_position is not None: - # We match a location for this scenario, create a scenario object so this scenario - # can be instantiated later - - if 'other_actors' in event: - other_vehicles = event['other_actors'] - else: - other_vehicles = None - scenario_subtype = RouteParser.get_scenario_type(scenario_name, match_position, - trajectory) - if scenario_subtype is None: - continue - scenario_description = { - 'name': scenario_name, - 'other_actors': other_vehicles, - 'trigger_position': waypoint, - 'scenario_type': scenario_subtype, # some scenarios have route dependent configs - } - - trigger_id = RouteParser.check_trigger_position(waypoint, existent_triggers) - if trigger_id is None: - # This trigger does not exist create a new reference on existent triggers - existent_triggers.update({latest_trigger_id: waypoint}) - # Update a reference for this trigger on the possible scenarios - possible_scenarios.update({latest_trigger_id: []}) - trigger_id = latest_trigger_id - # Increment the latest trigger - latest_trigger_id += 1 + return dist < DIST_THRESHOLD \ + and (angle_dist < ANGLE_THRESHOLD or angle_dist > (360 - ANGLE_THRESHOLD)) - possible_scenarios[trigger_id].append(scenario_description) + for route_transform, _ in route: + if is_trigger_close(trigger_transform, route_transform): + return True - return possible_scenarios, existent_triggers + return False diff --git a/srunner/tools/scenario_helper.py b/srunner/tools/scenario_helper.py index ecfe49935..4aa03b641 100644 --- a/srunner/tools/scenario_helper.py +++ b/srunner/tools/scenario_helper.py @@ -135,7 +135,7 @@ def get_crossing_point(actor): return crossing -def get_geometric_linear_intersection(ego_location, other_location): +def get_geometric_linear_intersection(ego_location, other_location, move_to_junction=False): """ Obtain a intersection point between two actor's location by using their waypoints (wp) @@ -144,11 +144,31 @@ def get_geometric_linear_intersection(ego_location, other_location): wp_ego_1 = CarlaDataProvider.get_map().get_waypoint(ego_location) wp_ego_2 = wp_ego_1.next(1)[0] + + if move_to_junction: + while True: + next_wp = wp_ego_2.next(1)[0] + if next_wp.is_junction: + break + else: + wp_ego_1 = wp_ego_2 + wp_ego_2 = next_wp + ego_1_loc = wp_ego_1.transform.location ego_2_loc = wp_ego_2.transform.location wp_other_1 = CarlaDataProvider.get_world().get_map().get_waypoint(other_location) wp_other_2 = wp_other_1.next(1)[0] + + if move_to_junction: + while True: + next_wp = wp_other_2.next(1)[0] + if next_wp.is_junction: + break + else: + wp_other_1 = wp_other_2 + wp_other_2 = next_wp + other_1_loc = wp_other_1.transform.location other_2_loc = wp_other_2.transform.location @@ -371,28 +391,27 @@ def generate_target_waypoint_in_route(waypoint, route): This method follow waypoints to a junction @returns a waypoint list according to turn input """ + target_waypoint = None wmap = CarlaDataProvider.get_map() reached_junction = False # Get the route location shortest_distance = float('inf') for index, route_pos in enumerate(route): - wp = route_pos[0] + route_location = route_pos[0].location trigger_location = waypoint.transform.location - dist_to_route = trigger_location.distance(wp) + dist_to_route = trigger_location.distance(route_location) if dist_to_route <= shortest_distance: closest_index = index shortest_distance = dist_to_route - route_location = route[closest_index][0] + route_location = route[closest_index][0].location index = closest_index - while True: - # Get the next route location - index = min(index + 1, len(route)) - route_location = route[index][0] - road_option = route[index][1] + for i in range(index, len(route)): + route_location = route[i][0].location + road_option = route[i][1] # Enter the junction if not reached_junction and (road_option in (RoadOption.LEFT, RoadOption.RIGHT, RoadOption.STRAIGHT)): @@ -400,9 +419,10 @@ def generate_target_waypoint_in_route(waypoint, route): # End condition for the behavior, at the end of the junction if reached_junction and (road_option not in (RoadOption.LEFT, RoadOption.RIGHT, RoadOption.STRAIGHT)): + target_waypoint = route_location break - return wmap.get_waypoint(route_location) + return wmap.get_waypoint(target_waypoint) def choose_at_junction(current_waypoint, next_choices, direction=0): @@ -472,8 +492,7 @@ def detect_lane_obstacle(actor, extension_factor=3, margin=1.02): """ This function identifies if an obstacle is present in front of the reference actor """ - world = CarlaDataProvider.get_world() - world_actors = world.get_actors().filter('vehicle.*') + world_actors = CarlaDataProvider.get_all_actors().filter('vehicle.*') actor_bbox = actor.bounding_box actor_transform = actor.get_transform() actor_location = actor_transform.location @@ -587,7 +606,7 @@ def get_closest_traffic_light(waypoint, traffic_lights=None): Checks all traffic lights part of 'traffic_lights', or all the town ones, if None are passed. """ if not traffic_lights: - traffic_lights = CarlaDataProvider.get_world().get_actors().filter('*traffic_light*') + traffic_lights = CarlaDataProvider.get_all_actors().filter('*traffic_light*') closest_dist = float('inf') closest_tl = None @@ -718,6 +737,71 @@ def get_distance_between_actors(current, target, distance_type="euclidianDistanc return distance +def get_same_dir_lanes(waypoint): + """ + Gets all the lanes with the same direction of the road of a wp. + Ordered from the edge lane to the center one (from outwards to inwards) + """ + same_dir_wps = [waypoint] + + # Check roads on the right + right_wp = waypoint + while True: + possible_right_wp = right_wp.get_right_lane() + if possible_right_wp is None or possible_right_wp.lane_type != carla.LaneType.Driving: + break + right_wp = possible_right_wp + same_dir_wps.append(right_wp) + + # Check roads on the left + left_wp = waypoint + while True: + possible_left_wp = left_wp.get_left_lane() + if possible_left_wp is None or possible_left_wp.lane_type != carla.LaneType.Driving: + break + if possible_left_wp.lane_id * left_wp.lane_id < 0: + break + left_wp = possible_left_wp + same_dir_wps.insert(0, left_wp) + + return same_dir_wps + + +def get_opposite_dir_lanes(waypoint): + """ + Gets all the lanes with opposite direction of the road of a wp + Ordered from the center lane to the edge one (from inwards to outwards) + """ + other_dir_wps = [] + other_dir_wp = None + + # Get the first lane of the opposite direction + left_wp = waypoint + while True: + possible_left_wp = left_wp.get_left_lane() + if possible_left_wp is None: + break + if possible_left_wp.lane_id * left_wp.lane_id < 0: + other_dir_wp = possible_left_wp + break + left_wp = possible_left_wp + + if not other_dir_wp: + return other_dir_wps + + # Check roads on the right + right_wp = other_dir_wp + while True: + if right_wp.lane_type == carla.LaneType.Driving: + other_dir_wps.append(right_wp) + possible_right_wp = right_wp.get_right_lane() + if possible_right_wp is None: + break + right_wp = possible_right_wp + + return other_dir_wps + + class RotatedRectangle(object): """ diff --git a/srunner/tools/scenario_parser.py b/srunner/tools/scenario_parser.py index 39f913801..0de98fa6c 100644 --- a/srunner/tools/scenario_parser.py +++ b/srunner/tools/scenario_parser.py @@ -13,6 +13,8 @@ import os import xml.etree.ElementTree as ET +import carla + from srunner.scenarioconfigs.scenario_configuration import ScenarioConfiguration, ActorConfigurationData from srunner.scenarioconfigs.route_scenario_configuration import RouteConfiguration @@ -24,7 +26,7 @@ class ScenarioConfigurationParser(object): """ @staticmethod - def parse_scenario_configuration(scenario_name, config_file_name): + def parse_scenario_configuration(scenario_name, additional_config_file_name): """ Parse all scenario configuration files at srunner/examples and the additional config files, providing a list of ScenarioConfigurations @return @@ -34,18 +36,18 @@ def parse_scenario_configuration(scenario_name, config_file_name): scenario that matches the scenario_name is parsed and returned. """ - list_of_config_files = glob.glob("{}/srunner/examples/*.xml".format(os.getenv('SCENARIO_RUNNER_ROOT', "./"))) - - if config_file_name != '': - list_of_config_files.append(config_file_name) - - single_scenario_only = True if scenario_name.startswith("group:"): - single_scenario_only = False + scenario_group = True scenario_name = scenario_name[6:] + else: + scenario_group = False scenario_configurations = [] + list_of_config_files = glob.glob("{}/srunner/examples/*.xml".format(os.getenv('SCENARIO_RUNNER_ROOT', "./"))) + if additional_config_file_name != '': + list_of_config_files.append(additional_config_file_name) + for file_name in list_of_config_files: tree = ET.parse(file_name) @@ -54,62 +56,54 @@ def parse_scenario_configuration(scenario_name, config_file_name): scenario_config_name = scenario.attrib.get('name', None) scenario_config_type = scenario.attrib.get('type', None) - if single_scenario_only: - # Check the scenario is the correct one - if scenario_config_name != scenario_name: - continue - else: - # Check the scenario is of the correct type - if scenario_config_type != scenario_name: - continue - - new_config = ScenarioConfiguration() - new_config.town = scenario.attrib.get('town', None) - new_config.name = scenario_config_name - new_config.type = scenario_config_type - new_config.other_actors = [] - new_config.ego_vehicles = [] - new_config.trigger_points = [] - - for weather in scenario.iter("weather"): - new_config.weather.cloudiness = float(weather.attrib.get("cloudiness", 0)) - new_config.weather.precipitation = float(weather.attrib.get("precipitation", 0)) - new_config.weather.precipitation_deposits = float(weather.attrib.get("precipitation_deposits", 0)) - new_config.weather.wind_intensity = float(weather.attrib.get("wind_intensity", 0.35)) - new_config.weather.sun_azimuth_angle = float(weather.attrib.get("sun_azimuth_angle", 0.0)) - new_config.weather.sun_altitude_angle = float(weather.attrib.get("sun_altitude_angle", 15.0)) - new_config.weather.fog_density = float(weather.attrib.get("fog_density", 0.0)) - new_config.weather.fog_distance = float(weather.attrib.get("fog_distance", 0.0)) - new_config.weather.wetness = float(weather.attrib.get("wetness", 0.0)) - - for ego_vehicle in scenario.iter("ego_vehicle"): - - new_config.ego_vehicles.append(ActorConfigurationData.parse_from_node(ego_vehicle, 'hero')) - new_config.trigger_points.append(new_config.ego_vehicles[-1].transform) - - for route in scenario.iter("route"): - route_conf = RouteConfiguration() - route_conf.parse_xml(route) - new_config.route = route_conf - - for other_actor in scenario.iter("other_actor"): - new_config.other_actors.append(ActorConfigurationData.parse_from_node(other_actor, 'scenario')) - - scenario_configurations.append(new_config) - + # Check that the scenario is the correct one + if not scenario_group and scenario_config_name != scenario_name: + continue + # Check that the scenario is of the correct type + elif scenario_group and scenario_config_type != scenario_name: + continue + + config = ScenarioConfiguration() + config.town = scenario.attrib.get('town') + config.name = scenario_config_name + config.type = scenario_config_type + + for elem in scenario.getchildren(): + # Elements with special parsing + if elem.tag == 'ego_vehicle': + config.ego_vehicles.append(ActorConfigurationData.parse_from_node(elem, 'hero')) + config.trigger_points.append(config.ego_vehicles[-1].transform) + elif elem.tag == 'other_actor': + config.other_actors.append(ActorConfigurationData.parse_from_node(elem, 'scenario')) + elif elem.tag == 'weather': + for weather_attrib in elem.attrib: + if hasattr(config.weather, weather_attrib): + setattr(config.weather, weather_attrib, float(elem.attrib[weather_attrib])) + else: + print(f"WARNING: Ignoring '{weather_attrib}', as it isn't a weather parameter") + + elif elem.tag == 'route': + route_conf = RouteConfiguration() + route_conf.parse_xml(elem) + config.route = route_conf + + # Any other possible element, add it as a config attribute + else: + config.other_parameters[elem.tag] = elem.attrib + + scenario_configurations.append(config) return scenario_configurations @staticmethod - def get_list_of_scenarios(config_file_name): + def get_list_of_scenarios(additional_config_file_name): """ Parse *all* config files and provide a list with all scenarios @return """ list_of_config_files = glob.glob("{}/srunner/examples/*.xml".format(os.getenv('SCENARIO_RUNNER_ROOT', "./"))) list_of_config_files += glob.glob("{}/srunner/examples/*.xosc".format(os.getenv('SCENARIO_RUNNER_ROOT', "./"))) - - if config_file_name != '': - list_of_config_files.append(config_file_name) + if additional_config_file_name != '': + list_of_config_files.append(additional_config_file_name) scenarios = [] for file_name in list_of_config_files: