Пример #1
0
class BeamNG_ACC_Test():
    def __init__(self, **kwargs):
        keys = kwargs.keys()
        if "test_controller" in keys:
            self.controller = kwargs.get("test_controller")
        else:
            self.controller = ACC_Test()
        config = BeamNG_Cruise_Control.Config(KP=CC_P,
                                              KI=CC_I,
                                              KD=CC_D,
                                              target=ACC_CAR_SPEED)
        config2 = BeamNG_Cruise_Control.Config(KP=CC_P,
                                               KI=CC_I,
                                               KD=CC_D,
                                               target=FRONT_CAR_SPEED)
        self.PID = BeamNG_Cruise_Control.PID_Controller(config=config)
        self.PID2 = BeamNG_Cruise_Control.PID_Controller(config=config2)

    def setup_bngServer(self):
        # Instantiate BeamNGpy instance running the simulator from the given path,
        if ('BNG_HOME' in os.environ):
            self.bng = BeamNGpy('localhost',
                                64256,
                                home=os.environ['BNG_HOME'])
        else:
            print(
                "WARNING no BNG_HOME is set! Make sure to set BeamNG.research path to research\trunk\ as environment variable (or write the path in the script by yourself)"
            )
            self.bng = BeamNGpy(
                'localhost',
                64256,
                home='C:\\Users\\Ingars\\Beamng-unlimited\\trunk')

    def setup_BeamNG(self):
        # Create a scenario in test map
        self.scenario = Scenario('cruise-control', 'example')

        # Create an ETK800 with the licence plate 'PID'
        self.vehicle = Vehicle('ACC', model='etk800', licence='ACC')

        electrics = Electrics()
        self.vehicle.attach_sensor('electrics',
                                   electrics)  # Cars electric system

        # Add it to our scenario at this position and rotation
        self.scenario.add_vehicle(self.vehicle, pos=ACC_CAR_POS, rot=(0, 0, 0))

        self.vehicle2 = Vehicle('FRONT', model='etk800', licence='FRONT')
        self.vehicle2.attach_sensor('electrics',
                                    electrics)  # Cars electric system
        self.scenario.add_vehicle(self.vehicle2,
                                  pos=FRONT_CAR_POS,
                                  rot=(0, 0, 180))
        # Place files defining our scenario for the simulator to read
        self.scenario.make(self.bng)

        # Launch BeamNG.research

        self.bng.open()

        # Load and start our scenario

        self.bng.load_scenario(self.scenario)
        self.bng.start_scenario()

    def runTest(self):
        self.bng.restart_scenario()
        self.bng.teleport_vehicle(self.vehicle,
                                  pos=ACC_CAR_POS,
                                  rot=(0, 0, 180))
        self.run()

    def run(self):
        self.setupTest()
        wanted_t = ACC_CAR_SPEED
        wanted_d = WANTED_DISTANCE

        soft_brake_d = wanted_d - (wanted_d / 2)

        wanted_d_buffer = wanted_d - (wanted_d / 5)
        # Wanted distance has a buffer of 25%, to compensate holding same constant speed at bigger distance

        deacc_d = wanted_d_buffer + 10
        # +10 meters is buffer for starting to decelerate towards front car speed and wanted distance.
        # the buffer should be calculated dynamically instead to compensate for different speeds
        while self.controller.ended == False:
            start_time = datetime.datetime.now()
            sensors = self.bng.poll_sensors(self.vehicle)
            position = self.vehicle.state['pos']
            position2 = self.vehicle2.state['pos']
            sensors2 = self.bng.poll_sensors(
                self.vehicle2
            )  # replace with self.vehicle.update_vehicle when not using pre-generated coordinates of vehicle to follow

            wheel_speed = sensors['electrics']['values']['wheelspeed']

            distance = self.controller.setDistance(position, position2)

            front_car_speed = self.controller.getFrontCarSpeed(wheel_speed)
            curr_target = self.PID.get_target()
            print("distance: " + str(distance))
            #FORMULA USED for deacceleration = front_car_speed^2 = wheel_speed^2 + (distance-20)*2*a
            #d = (front_car_speed - wheel_speed) / (-0.3 * 2) # 0.3 deacceleration? distance for reducing speed to front cars speed with -0.3 acceleration
            #print("d: " + str(d))
            if distance < 5:  # 5 is manually set as last allowed distance between both cars, will not work good if ACC car is driving too fast. Would be better to calculate it as braking distance.
                print("brake1")
                self.vehicle.control(brake=1)
                self.vehicle.control(throttle=0)
            elif distance < soft_brake_d:
                print("brake1")
                self.vehicle.control(
                    brake=0.1
                )  #Softness of brake could also be calculated dynamically to compensate different speeds
                self.vehicle.control(throttle=0)
            else:
                if distance <= wanted_d_buffer:
                    print("wanted")
                    if front_car_speed > curr_target + 3.5 or front_car_speed < curr_target - 2:  #or front_car_speed < curr_target - 1   // calibrate 3.5 and 2
                        self.PID.change_target(max(front_car_speed, 0))
                elif distance <= deacc_d:
                    a = (front_car_speed - wheel_speed) / (
                        (distance - wanted_d_buffer) * 2)
                    print("a:" + str(a))
                    self.PID.change_target(a + wheel_speed)
                elif curr_target != wanted_t:
                    self.PID.change_target(wanted_t)
                print("throttle1")
                value = self.PID.calculate_throttle(wheel_speed)
                value = min(1, value)
                self.vehicle.control(brake=0)
                self.vehicle.control(throttle=value)
            #PID for front car
            wheel_speed2 = sensors2['electrics']['values']['wheelspeed']
            # print("real front:" + str(wheel_speed2))
            value2 = self.PID2.calculate_throttle(wheel_speed2)
            value2 = min(1, value2)
            self.vehicle2.control(brake=0)
            self.vehicle2.control(throttle=value2)

            elapsed_time = datetime.datetime.now() - start_time
            while (elapsed_time.total_seconds() * 1000) < 100:
                elapsed_time = datetime.datetime.now() - start_time
            elapsed_total = self.controller.calculateElapsed()
            # Change front car speed after 10 seconds and after 20 seconds
            if elapsed_total.total_seconds() > float(10):
                self.PID2.change_target(20)
            if elapsed_total.total_seconds() > float(20):
                self.PID2.change_target(10)
        print("Ending Test")

    def close(self):
        self.bng.close()

    def setupTest(self):
        self.vehicle.update_vehicle()
        self.controller.last_position = self.vehicle.state['pos']
        self.controller.start()
Пример #2
0
class WCARaceGeometry(gym.Env):
    sps = 50
    rate = 5

    front_dist = 800
    front_step = 100
    trail_dist = 104
    trail_step = 13

    starting_proj = 1710
    max_damage = 100

    def __init__(self, host='localhost', port=64256):
        self.steps = WCARaceGeometry.sps // WCARaceGeometry.rate
        self.host = host
        self.port = port

        self.action_space = self._action_space()
        self.observation_space = self._observation_space()

        self.episode_steps = 0
        self.spine = None
        self.l_edge = None
        self.r_edge = None
        self.polygon = None

        front_factor = WCARaceGeometry.front_dist / WCARaceGeometry.front_step
        trail_factor = WCARaceGeometry.trail_dist / WCARaceGeometry.trail_step
        self.front = lambda step: +front_factor * step
        self.trail = lambda step: -trail_factor * step

        self.bng = BeamNGpy(self.host, self.port)

        self.vehicle = Vehicle('racecar',
                               model='sunburst',
                               licence='BEAMNG',
                               colour='red',
                               partConfig='vehicles/sunburst/hillclimb.pc')

        electrics = Electrics()
        damage = Damage()
        self.vehicle.attach_sensor('electrics', electrics)
        self.vehicle.attach_sensor('damage', damage)

        scenario = Scenario('west_coast_usa', 'wca_race_geometry_v0')
        scenario.add_vehicle(self.vehicle,
                             pos=(394.5, -247.925, 145.25),
                             rot=(0, 0, 90))

        scenario.make(self.bng)

        self.bng.open(launch=True)
        self.bng.set_deterministic()
        self.bng.set_steps_per_second(WCARaceGeometry.sps)
        self.bng.load_scenario(scenario)

        self._build_racetrack()

        self.observation = None
        self.last_observation = None
        self.last_spine_proj = None

        self.bng.start_scenario()
        self.bng.pause()

    def __del__(self):
        self.bng.close()

    def _build_racetrack(self):
        roads = self.bng.get_roads()
        track = roads['race_ref']
        l_vtx = []
        s_vtx = []
        r_vtx = []
        for right, middle, left in track:
            r_vtx.append(right)
            s_vtx.append(middle)
            l_vtx.append(left)

        self.spine = LinearRing(s_vtx)
        self.r_edge = LinearRing(r_vtx)
        self.l_edge = LinearRing(l_vtx)

        r_vtx = [v[0:2] for v in r_vtx]
        l_vtx = [v[0:2] for v in l_vtx]
        self.polygon = Polygon(l_vtx, holes=[r_vtx])

    def _action_space(self):
        action_lo = [-1., -1.]
        action_hi = [+1., +1.]
        return gym.spaces.Box(np.array(action_lo),
                              np.array(action_hi),
                              dtype=float)

    def _observation_space(self):
        # n vertices of left and right polylines ahead and behind, 3 floats per
        # vtx
        scope = WCARaceGeometry.trail_step + WCARaceGeometry.front_step
        obs_lo = [
            -np.inf,
        ] * scope * 3
        obs_hi = [
            np.inf,
        ] * scope * 3
        obs_lo.extend([
            -np.inf,  # Distance to left edge
            -np.inf,  # Distance to right edge
            -2 * np.pi,  # Inclination
            -2 * np.pi,  # Angle
            -2 * np.pi,  # Vertical angle
            -np.inf,  # Spine speed
            0,  # RPM
            -1,  # Gear
            0,  # Throttle
            0,  # Brake
            -1.0,  # Steering
            0,  # Wheel speed
            -np.inf,  # Altitude
        ])
        obs_hi.extend([
            np.inf,  # Distance to left edge
            np.inf,  # Distance to right edge
            2 * np.pi,  # Inclincation
            2 * np.pi,  # Angle
            2 * np.pi,  # Vertical angle
            np.inf,  # Spine speed
            np.inf,  # RPM
            8,  # Gear
            1.0,  # Throttle
            1.0,  # Brake
            1.0,  # Steering
            np.inf,  # Wheel speed
            np.inf,  # Altitude
        ])
        return gym.spaces.Box(np.array(obs_lo), np.array(obs_hi), dtype=float)

    def _make_commands(self, action):
        brake = 0
        throttle = action[1]
        steering = action[0]
        if throttle < 0:
            brake = -throttle
            throttle = 0

        self.vehicle.control(steering=steering, throttle=throttle, brake=brake)

    def _project_vehicle(self, pos):
        r_proj = self.r_edge.project(pos)
        r_proj = self.r_edge.interpolate(r_proj)
        l_proj = self.l_edge.project(r_proj)
        l_proj = self.l_edge.interpolate(l_proj)
        s_proj = self.spine.project(r_proj)
        s_proj = self.spine.interpolate(s_proj)
        return l_proj, s_proj, r_proj

    def _get_vehicle_angles(self, vehicle_pos, spine_seg):
        spine_beg = spine_seg.coords[+0]
        spine_end = spine_seg.coords[-1]
        spine_angle = np.arctan2(spine_end[1] - spine_beg[1],
                                 spine_end[0] - spine_beg[0])
        vehicle_angle = self.vehicle.state['dir'][0:2]
        vehicle_angle = np.arctan2(vehicle_angle[1], vehicle_angle[0])

        vehicle_angle = normalise_angle(vehicle_angle - spine_angle)
        vehicle_angle -= np.pi

        elevation = np.arctan2(spine_beg[2] - spine_end[2], spine_seg.length)
        vehicle_elev = self.vehicle.state['dir']
        vehicle_elev = np.arctan2(vehicle_elev[2],
                                  np.linalg.norm(vehicle_elev))

        return vehicle_angle, vehicle_elev, elevation

    def _wrap_length(self, target):
        length = self.spine.length
        while target < 0:
            target += length
        while target > length:
            target -= length
        return target

    def _gen_track_scope_loop(self, it, fn, base, s_scope, s_width):
        for step in it:
            distance = base + fn(step)
            distance = self._wrap_length(distance)
            s_proj = self.spine.interpolate(distance)
            s_scope.append(s_proj)
            l_proj = self.l_edge.project(s_proj)
            l_proj = self.l_edge.interpolate(l_proj)
            r_proj = self.r_edge.project(s_proj)
            r_proj = self.r_edge.interpolate(r_proj)
            width = l_proj.distance(r_proj)
            s_width.append(width)

    def _gen_track_scope(self, pos, spine_seg):
        s_scope = []
        s_width = []

        base = self.spine.project(pos)

        it = range(WCARaceGeometry.trail_step, 0, -1)
        self._gen_track_scope_loop(it, self.trail, base, s_scope, s_width)

        it = range(1)
        self._gen_track_scope_loop(it, lambda x: x, base, s_scope, s_width)

        it = range(WCARaceGeometry.front_step + 1)
        self._gen_track_scope_loop(it, self.front, base, s_scope, s_width)

        s_proj = self.spine.interpolate(base)
        offset = (-s_proj.x, -s_proj.y, -s_proj.z)
        s_line = LineString(s_scope)
        s_line = affinity.translate(s_line, *offset)

        spine_beg = spine_seg.coords[+0]
        spine_end = spine_seg.coords[-1]
        direction = [spine_end[i] - spine_beg[i] for i in range(3)]
        angle = np.arctan2(direction[1], direction[0]) + np.pi / 2

        s_line = affinity.rotate(s_line,
                                 -angle,
                                 origin=(0, 0),
                                 use_radians=True)

        ret = list()
        s_scope = s_line.coords
        for idx in range(1, len(s_scope) - 1):
            curvature = calculate_curvature(s_scope, idx)
            inclination = calculate_inclination(s_scope, idx)
            width = s_width[idx]
            ret.append(curvature)
            ret.append(inclination)
            ret.append(width)

        return ret

    def _spine_project_vehicle(self, vehicle_pos):
        proj = self.spine.project(vehicle_pos) - WCARaceGeometry.starting_proj
        if proj < 0:
            proj += self.spine.length
        proj = self.spine.length - proj
        return proj

    def _get_spine_speed(self, vehicle_pos, vehicle_dir, spine_seg):
        spine_beg = spine_seg.coords[0]
        future_pos = Point(vehicle_pos.x + vehicle_dir[0],
                           vehicle_pos.y + vehicle_dir[1],
                           vehicle_pos.z + vehicle_dir[2])
        spine_end = self.spine.project(future_pos)
        spine_end = self.spine.interpolate(spine_end)
        return spine_end.distance(Point(*spine_beg))

    def _make_observation(self, sensors):
        electrics = sensors['electrics']['values']

        vehicle_dir = self.vehicle.state['dir']
        vehicle_pos = self.vehicle.state['pos']
        vehicle_pos = Point(*vehicle_pos)

        spine_beg = self.spine.project(vehicle_pos)
        spine_end = spine_beg
        spine_end += WCARaceGeometry.front_dist / WCARaceGeometry.front_step
        spine_beg = self.spine.interpolate(spine_beg)
        spine_end = self.spine.interpolate(spine_end)
        spine_seg = LineString([spine_beg, spine_end])

        spine_speed = self._get_spine_speed(vehicle_pos, vehicle_dir,
                                            spine_seg)

        l_dist = self.l_edge.distance(vehicle_pos)
        r_dist = self.r_edge.distance(vehicle_pos)

        angle, vangle, elevation = self._get_vehicle_angles(
            vehicle_pos, spine_seg)

        l_proj, s_proj, r_proj = self._project_vehicle(vehicle_pos)
        s_scope = self._gen_track_scope(vehicle_pos, spine_seg)

        obs = list()
        obs.extend(s_scope)
        obs.append(l_dist)
        obs.append(r_dist)
        obs.append(elevation)
        obs.append(angle)
        obs.append(vangle)
        obs.append(spine_speed)
        obs.append(electrics['rpm'])
        obs.append(electrics['gearIndex'])
        obs.append(electrics['throttle'])
        obs.append(electrics['brake'])
        obs.append(electrics['steering'])
        obs.append(electrics['wheelspeed'])
        obs.append(electrics['altitude'])

        return np.array(obs)

    def _compute_reward(self, sensors):
        damage = sensors['damage']
        vehicle_pos = self.vehicle.state['pos']
        vehicle_pos = Point(*vehicle_pos)

        if damage['damage'] > WCARaceGeometry.max_damage:
            return -1, True

        if not self.polygon.contains(Point(vehicle_pos.x, vehicle_pos.y)):
            return -1, True

        score, done = -1, False
        spine_proj = self._spine_project_vehicle(vehicle_pos)
        if self.last_spine_proj is not None:
            diff = spine_proj - self.last_spine_proj
            if diff >= -0.10:
                if diff < 0.5:
                    return -1, False
                else:
                    score, done = diff / self.steps, False
            elif np.abs(diff) > self.spine.length * 0.95:
                score, done = 1, True
            else:
                score, done = -1, True
        self.last_spine_proj = spine_proj
        return score, done

    def reset(self):
        self.episode_steps = 0
        self.vehicle.control(throttle=0, brake=0, steering=0)
        self.bng.restart_scenario()
        self.bng.step(30)
        self.bng.pause()
        self.vehicle.set_shift_mode(3)
        self.vehicle.control(gear=2)
        sensors = self.bng.poll_sensors(self.vehicle)
        self.observation = self._make_observation(sensors)
        vehicle_pos = self.vehicle.state['pos']
        vehicle_pos = Point(*vehicle_pos)
        self.last_spine_proj = self._spine_project_vehicle(vehicle_pos)
        return self.observation

    def advance(self):
        self.bng.step(self.steps, wait=False)

    def observe(self):
        sensors = self.bng.poll_sensors(self.vehicle)
        new_observation = self._make_observation(sensors)
        return new_observation, sensors

    def step(self, action):
        action = [*np.clip(action, -1, 1), action[0], action[1]]
        action = [float(v) for v in action]

        self.episode_steps += 1

        self._make_commands(action)
        self.advance()
        new_observation, sensors = self.observe()
        if self.observation is not None:
            self.last_observation = self.observation
        self.observation = new_observation
        score, done = self._compute_reward(sensors)

        print((' A: {:5.2f}  B: {:5.2f} '
               ' S: {:5.2f}  T: {:5.2f}  R: {:5.2f}').format(
                   action[2], action[3], action[0], action[1], score))

        # if done:
        #     self.bng.step(WCARaceGeometry.sps * 1)

        return self.observation, score, done, {}
Пример #3
0
class BeamNG_Cruise_Controller_Test():
    def __init__(self, **kwargs):
        keys = kwargs.keys()
        if "test_controller" in keys:
            self.controller = kwargs.get("test_controller")
        else:
            self.controller = PID_Controller_Test()
        if "testing_times" in keys:
            self.testing_times = kwargs.get("testing_times")
        else:
            self.testing_times = 1
        if "test_name" in keys:
            self.test_name = kwargs.get("test_name")
        else:
            self.test_name = "test"
        if "targets" in keys:
            self.targets = kwargs.get("targets")
        else:
            self.targets = [0]

    def setup_bngServer(self):
        # Instantiate BeamNGpy instance running the simulator from the given path,
        if ('BNG_HOME' in os.environ):
            self.bng = BeamNGpy('localhost',
                                64256,
                                home=os.environ['BNG_HOME'])
        else:
            print(
                "WARNING no BNG_HOME is set! Make sure to set BeamNG.research path to research\trunk\ as environment variable (or write the path in the script by yourself)"
            )
            self.bng = BeamNGpy('localhost', 64256, home='')

    def setup_BeamNG(self):
        # Create a scenario in test map
        scenario = Scenario('cruise-control', 'example')

        # Create an ETK800 with the licence plate 'PID'
        self.vehicle = Vehicle('ego_vehicle', model='etk800', licence='PID')

        electrics = Electrics()
        self.vehicle.attach_sensor('electrics',
                                   electrics)  # Cars electric system

        # Add it to our scenario at this position and rotation
        scenario.add_vehicle(self.vehicle,
                             pos=(406.787, 1115.518, 0),
                             rot=(0, 0, 0))

        # Place files defining our scenario for the simulator to read
        scenario.make(self.bng)

        # Launch BeamNG.research

        self.bng.open()

        # Load and start our scenario

        self.bng.load_scenario(scenario)
        self.bng.start_scenario()

    def runTest(self, test_K_values, test_name):
        for test_K_value in test_K_values:
            KP_in, KI_in, KD_in = map(float, test_K_value)

            if KP_in == 0 and KI_in == 0 and KD_in == 0:
                config = Config()
                controller = On_Off_Controller(config=config)
            else:
                config = Config(KP=KP_in, KI=KI_in, KD=KD_in)
                controller = PID_Controller(config=config)
            self.controller.controller = controller
            self.test_name = str(test_name) + "_" + str(KP_in) + "_" + str(
                KI_in) + "_" + str(KD_in)
            self.runTestForPID()

    def runTestForPID(self):
        for speed in self.targets:
            self.controller.setTarget(speed)
            self.controller.setTime(float(30))
            #self.runTestOfType("up_{0}_".format(speed), (406.787, 700.517, 0.214829), (0, 0, 0))
            self.runTestOfType("straight_{0}_".format(speed),
                               (406.787, 715.517, 0.214829), (0, 0, 180))
            #self.runTestOfType("down_{0}_".format(speed), (406.787, 304.896, 100.211), (0, 0, 180))

    def runTestOfType(self, type, pos, rot):
        i = 1
        while i <= self.testing_times:
            self.controller.newLogFile("../logs/" + self.test_name + "_" +
                                       str(type) + "_" + str(i) + ".txt")
            self.bng.restart_scenario()
            self.bng.teleport_vehicle(self.vehicle, pos=pos, rot=rot)
            self.run()
            i += 1

    def run(self):
        self.controller.start()
        while self.controller.ended == False:
            start_time = datetime.datetime.now()
            sensors = self.bng.poll_sensors(self.vehicle)
            wheel_speed = sensors['electrics']['values']['wheelspeed']
            value = self.controller.calculate_speed_with_logs(wheel_speed)
            if value <= 0:
                value = max(-1, value - 0.1) * -1
                self.vehicle.control(brake=value)
                self.vehicle.control(throttle=0)
            else:
                value = min(1, value)
                self.vehicle.control(brake=0)
            self.vehicle.control(throttle=value)
            elapsed_time = datetime.datetime.now() - start_time
            while (elapsed_time.total_seconds() * 1000) < 100:
                elapsed_time = datetime.datetime.now() - start_time
        print("Ending Test")

    def close(self):
        self.bng.close()
Пример #4
0
class ImpactGenerator:
    parts = [
        'etk800_bumper_F',
        'etk800_bumperbar_F',
        'etk800_bumper_R',
        'etk800_fender_L',
        'etk800_fender_R',
        'etk800_hood',
        'etk800_towhitch',
        'etk800_steer',
        'etk800_radiator',
        'etk800_roof_wagon',
        'wheel_F_5',
    ]

    emptyable = {
        'etk800_bumperbar_F',
        'etk800_towhitch',
    }

    wca = {
        'level': 'west_coast_usa',
        'a_spawn': (-270.75, 678, 74.9),
        'b_spawn': (-260.25, 678, 74.9),
        'pole_pos': (-677.15, 848, 75.1),
        'linear_pos_a': (-630, 65, 103.4),
        'linear_pos_b': (-619, 77, 102.65),
        'linear_rot_b': (0, 0, 45.5),
        't_pos_a': (-440, 688, 75.1),
        't_pos_b': (-453, 700, 75.1),
        't_rot_b': (0, 0, 315),
        'ref_pos': (-18, 610, 75),
    }

    smallgrid = {
        'level': 'smallgrid',
        'a_spawn': (-270.75, 678, 0.1),
        'b_spawn': (-260.25, 678, 0.1),
        'pole_pos': (-677.15, 848, 0.1),
        'pole': (-682, 842, 0),
        'linear_pos_a': (-630, 65, 0.1),
        'linear_pos_b': (-619, 77, 0.1),
        'linear_rot_b': (0, 0, 45.5),
        't_pos_a': (-440, 688, 0.1),
        't_pos_b': (-453, 700, 0.1),
        't_rot_b': (0, 0, 315),
        'ref_pos': (321, 321, 0.1),
    }

    def __init__(self,
                 bng_home,
                 output,
                 config,
                 poolsize=2,
                 smallgrid=False,
                 sim_mtx=None,
                 similarity=0.5,
                 random_select=False,
                 single=False):
        self.bng_home = bng_home
        self.output = output
        self.config = config
        self.smallgrid = smallgrid
        self.single = single

        self.impactgen_mats = None
        if smallgrid:
            mats = materialmngr.get_impactgen_materials(bng_home)
            self.impactgen_mats = sorted(list(mats))

        self.poolsize = poolsize
        self.similarity = similarity
        self.sim_mtx = sim_mtx
        self.random_select = random_select

        self.pole_space = None
        self.t_bone_space = None
        self.linear_space = None
        self.nocrash_space = None

        self.post_space = None

        self.total_possibilities = 0

        self.bng = BeamNGpy('localhost', 64256, home=bng_home)

        self.scenario = None

        scenario_props = ImpactGenerator.wca
        if smallgrid:
            scenario_props = ImpactGenerator.smallgrid

        self.vehicle_a = Vehicle('vehicle_a', model='etk800')
        self.vehicle_b = Vehicle('vehicle_b', model='etk800')

        self.scenario = Scenario(scenario_props['level'], 'impactgen')
        self.scenario.add_vehicle(self.vehicle_a,
                                  pos=scenario_props['a_spawn'],
                                  rot=(0, 0, 0))
        self.scenario.add_vehicle(self.vehicle_b,
                                  pos=scenario_props['b_spawn'],
                                  rot=(0, 0, 0))

        self.vehicle_a_parts = defaultdict(set)
        self.vehicle_a_config = None
        self.vehicle_b_config = None

    def generate_colors(self):
        return copy.deepcopy(self.config['colors'])

    def generate_nocrash_space(self, props):
        nocrash_options = []
        for part in ImpactGenerator.parts:  # Vary each configurable part
            nocrash_options.append(self.vehicle_a_parts[part])
        self.nocrash_space = OptionSpace(nocrash_options)

    def generate_pole_space(self, props):
        pole_options = [(False, True)]  # Vehicle facing forward/backward
        pole_options.append(np.linspace(-0.75, 0.75, 5))  # Position offset
        pole_options.append(np.linspace(0.15, 0.5, 4))  # Throttle intensity
        for part in ImpactGenerator.parts:  # Vary each configurable part
            pole_options.append(self.vehicle_a_parts[part])
        self.pole_space = OptionSpace(pole_options)

    def generate_t_bone_space(self, props):
        t_options = [(False, True)]  # Vehicle hit left/right
        t_options.append(np.linspace(-30, 30, 11))  # A rotation offset
        t_options.append(np.linspace(-1.5, 1.5, 5))  # B pos. offset
        t_options.append(np.linspace(0.2, 0.5, 4))  # B throttle
        for part in ImpactGenerator.parts:
            t_options.append(self.vehicle_a_parts[part])
        self.t_bone_space = OptionSpace(t_options)

    def generate_linear_space(self, props):
        linear_options = [(False, True)]  # Vehicle hit front/back
        linear_options.append(np.linspace(-15, 15, 5))  # A rot. offset
        linear_options.append(np.linspace(-1.33, 1.33, 5))  # B pos. offset
        linear_options.append(np.linspace(0.25, 0.5, 4))  # B throttle
        for part in ImpactGenerator.parts:
            linear_options.append(self.vehicle_a_parts[part])
        self.linear_space = OptionSpace(linear_options)

    def get_material_options(self):
        if not self.random_select:
            selected = materialmngr.pick_materials(self.impactgen_mats,
                                                   self.sim_mtx,
                                                   poolsize=self.poolsize,
                                                   similarity=self.similarity)
            if selected is None:
                log.info('Could not find material pool through similarity. '
                         'Falling back to random select.')
        else:
            selected = random.sample(self.impactgen_mats, self.poolsize)

        return selected

    def generate_post_space(self):
        colors = self.generate_colors()
        post_options = []
        post_options.append(self.config['times'])
        if self.smallgrid:
            post_options.append([0])
            post_options.append([0])
        else:
            post_options.append(self.config['clouds'])
            post_options.append(self.config['fogs'])
        post_options.append(colors)
        if self.smallgrid:
            mats = self.get_material_options()
            if mats is not None:
                post_options.append(list(mats))
                post_options.append(list(mats))
        return OptionSpace(post_options)

    def generate_spaces(self):
        props = ImpactGenerator.wca
        if self.smallgrid:
            props = ImpactGenerator.smallgrid

        self.generate_nocrash_space(props)
        self.generate_t_bone_space(props)
        self.generate_linear_space(props)
        self.generate_pole_space(props)

    def scan_parts(self, parts, known=set()):
        with open('out.json', 'w') as outfile:
            outfile.write(json.dumps(parts, indent=4, sort_keys=True))

        for part_type in ImpactGenerator.parts:
            options = parts[part_type]
            self.vehicle_a_parts[part_type].update(options)

    def init_parts(self):
        self.vehicle_a_config = self.vehicle_a.get_part_config()
        self.vehicle_b_config = self.vehicle_b.get_part_config()

        b_parts = self.vehicle_b_config['parts']
        b_parts['etk800_licenseplate_R'] = 'etk800_licenseplate_R_EU'
        b_parts['etk800_licenseplate_F'] = 'etk800_licenseplate_F_EU'
        b_parts['licenseplate_design_2_1'] = 'license_plate_germany_2_1'

        options = self.vehicle_a.get_part_options()
        self.scan_parts(options)

        for k in self.vehicle_a_parts.keys():
            self.vehicle_a_parts[k] = list(self.vehicle_a_parts[k])
            if k in ImpactGenerator.emptyable:
                self.vehicle_a_parts[k].append('')

    def init_settings(self):
        self.bng.set_particles_enabled(False)

        self.generate_spaces()

        log.info('%s pole crash possibilities.', self.pole_space.count)
        log.info('%s T-Bone crash possibilities.', self.t_bone_space.count)
        log.info('%s parallel crash possibilities.', self.linear_space.count)
        log.info('%s no crash possibilities.', self.nocrash_space.count)

        self.total_possibilities = \
            self.pole_space.count + \
            self.t_bone_space.count + \
            self.linear_space.count + \
            self.nocrash_space.count
        log.info('%s total incidents possible.', self.total_possibilities)

    def get_vehicle_config(self, setting):
        parts = dict()
        for idx, part in enumerate(ImpactGenerator.parts):
            parts[part] = setting[idx]
        refwheel = parts['wheel_F_5']
        parts['wheel_R_5'] = refwheel.replace('_F', '_R')

        # Force licence plate to always be German
        parts['etk800_licenseplate_R'] = 'etk800_licenseplate_R_EU'
        parts['etk800_licenseplate_F'] = 'etk800_licenseplate_F_EU'
        parts['licenseplate_design_2_1'] = 'license_plate_germany_2_1'

        config = copy.deepcopy(self.vehicle_a_config)
        config['parts'] = parts
        return config

    def set_annotation_paths(self):
        part_path = os.path.join(self.bng_home, PART_ANNOTATIONS)
        part_path = os.path.abspath(part_path)
        obj_path = os.path.join(self.bng_home, OBJ_ANNOTATIONS)
        obj_path = os.path.abspath(obj_path)

        req = dict(type='ImpactGenSetAnnotationPaths')
        req['partPath'] = part_path
        req['objPath'] = obj_path
        self.bng.send(req)
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenAnnotationPathsSet'

    def set_image_properties(self):
        req = dict(type='ImpactGenSetImageProperties')
        req['imageWidth'] = self.config['imageWidth']
        req['imageHeight'] = self.config['imageHeight']
        req['colorFmt'] = self.config['colorFormat']
        req['annotFmt'] = self.config['annotFormat']
        req['radius'] = self.config['cameraRadius']
        req['height'] = self.config['cameraHeight']
        req['fov'] = self.config['fov']

        self.bng.send(req)
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenImagePropertiesSet'

    def setup(self):
        self.scenario.make(self.bng)
        log.debug('Loading scenario...')
        self.bng.load_scenario(self.scenario)
        log.debug('Setting steps per second...')
        self.bng.set_steps_per_second(50)
        log.debug('Enabling deterministic mode...')
        self.bng.set_deterministic()
        log.debug('Starting scenario...')
        self.bng.start_scenario()
        log.debug('Scenario started. Sleeping 20s.')
        time.sleep(20)

        self.init_parts()
        self.init_settings()

        log.debug('Setting annotation properties.')
        self.set_annotation_paths()
        self.set_image_properties()

    def settings_exhausted(self):
        return self.t_bone_space.exhausted() and \
            self.linear_space.exhausted() and \
            self.pole_space.exhausted() and \
            self.nocrash_space.exhausted()

    def set_post_settings(self, vid, settings):
        req = dict(type='ImpactGenPostSettings')
        req['ego'] = vid
        req['time'] = settings[0]
        req['clouds'] = settings[1]
        req['fog'] = settings[2]
        req['color'] = settings[3]
        if len(settings) > 4:
            req['skybox'] = settings[4]
        if len(settings) > 5:
            req['ground'] = settings[5]
        self.bng.send(req)
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenPostSet'

    def finished_producing(self):
        req = dict(type='ImpactGenOutputGenerated')
        self.bng.send(req)
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenZipGenerated'
        return resp['state']

    def produce_output(self, color_name, annot_name):
        while not self.finished_producing():
            time.sleep(0.2)

        req = dict(type='ImpactGenGenerateOutput')
        req['colorName'] = color_name
        req['annotName'] = annot_name
        self.bng.send(req)
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenZipStarted'
        'ImpactGenZipStarted'

    def capture_post(self, crash_setting):
        log.info('Enumerating post-crash settings and capturing output.')
        self.bng.switch_vehicle(self.vehicle_a)
        ref_pos = ImpactGenerator.wca['ref_pos']
        if self.smallgrid:
            ref_pos = ImpactGenerator.smallgrid['ref_pos']
        self.bng.teleport_vehicle(self.vehicle_a, ref_pos)
        self.bng.teleport_vehicle(self.vehicle_b, (10000, 10000, 10000))

        self.bng.step(50, wait=True)
        self.bng.pause()

        self.post_space = self.generate_post_space()
        while not self.post_space.exhausted():
            post_setting = self.post_space.sample_new()

            scenario = [[str(s) for s in crash_setting]]
            scenario.append([str(s) for s in post_setting])
            key = str(scenario).encode('ascii')
            key = hashlib.sha512(key).hexdigest()[:30]

            t = int(time.time())
            color_name = '{}_{}_0_image.zip'.format(t, key)
            annot_name = '{}_{}_0_annotation.zip'.format(t, key)
            color_name = os.path.join(self.output, color_name)
            annot_name = os.path.join(self.output, annot_name)

            log.info('Setting post settings.')
            self.set_post_settings(self.vehicle_a.vid, post_setting)
            log.info('Producing output.')
            self.produce_output(color_name, annot_name)

            if self.single:
                break

        self.bng.resume()

    def run_t_bone_crash(self):
        log.info('Running t-bone crash setting.')
        if self.t_bone_space.exhausted():
            log.debug('T-Bone crash setting exhausted.')
            return None

        props = ImpactGenerator.wca
        if self.smallgrid:
            props = ImpactGenerator.smallgrid

        setting = self.t_bone_space.sample_new()
        side, angle, offset, throttle = setting[:4]
        config = setting[4:]
        config = self.get_vehicle_config(config)

        if side:
            angle += 225
        else:
            angle += 45

        pos_a = props['t_pos_a']

        rot_b = props['t_rot_b']
        pos_b = list(props['t_pos_b'])
        pos_b[0] += offset

        req = dict(type='ImpactGenRunTBone')
        req['ego'] = self.vehicle_a.vid
        req['other'] = self.vehicle_b.vid
        req['config'] = config
        req['aPosition'] = pos_a
        req['angle'] = angle
        req['bPosition'] = pos_b
        req['bRotation'] = rot_b
        req['throttle'] = throttle
        log.debug('Sending t-bone crash config.')
        self.bng.send(req)
        log.debug('T-Bone crash response received.')
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenTBoneRan'

        return setting

    def run_linear_crash(self):
        log.info('Running linear crash setting.')
        if self.linear_space.exhausted():
            log.debug('Linear crash settings exhausted.')
            return None

        props = ImpactGenerator.wca
        if self.smallgrid:
            props = ImpactGenerator.smallgrid

        setting = self.linear_space.sample_new()
        back, angle, offset, throttle = setting[:4]
        config = setting[4:]
        config = self.get_vehicle_config(config)

        if back:
            angle += 225
        else:
            offset += 1.3
            angle += 45

        pos_a = props['linear_pos_a']

        rot_b = props['linear_rot_b']
        pos_b = list(props['linear_pos_b'])
        pos_b[0] += offset

        req = dict(type='ImpactGenRunLinear')
        req['ego'] = self.vehicle_a.vid
        req['other'] = self.vehicle_b.vid
        req['config'] = config
        req['aPosition'] = pos_a
        req['angle'] = angle
        req['bPosition'] = pos_b
        req['bRotation'] = rot_b
        req['throttle'] = throttle
        log.debug('Sending linear crash config.')
        self.bng.send(req)
        log.debug('Linear crash response received.')
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenLinearRan'

        return setting

    def run_pole_crash(self):
        log.info('Running pole crash setting.')
        if self.pole_space.exhausted():
            log.debug('Pole crash settings exhausted.')
            return None

        props = ImpactGenerator.wca
        if self.smallgrid:
            props = ImpactGenerator.smallgrid

        setting = self.pole_space.sample_new()
        back, offset, throttle = setting[:3]
        config = setting[3:]
        config = self.get_vehicle_config(config)

        angle = 45
        if back:
            angle = 225
            offset += 0.85
            throttle = -throttle

        pos = list(props['pole_pos'])
        pos[0] += offset

        req = dict(type='ImpactGenRunPole')
        req['ego'] = self.vehicle_a.vid
        req['config'] = config
        req['position'] = pos
        req['angle'] = angle
        req['throttle'] = throttle
        if self.smallgrid:
            req['polePosition'] = props['pole']
        log.debug('Sending pole crash config.')
        self.bng.send(req)
        log.debug('Got pole crash response.')
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenPoleRan'

        return setting

    def run_no_crash(self):
        log.info('Running non-crash scenario.')
        if self.nocrash_space.exhausted():
            return None

        setting = self.nocrash_space.sample_new()
        log.info('Got new setting: %s', setting)

        vehicle_config = self.get_vehicle_config(setting)
        log.info('Got new vehicle config: %s', vehicle_config)

        req = dict(type='ImpactGenRunNonCrash')
        req['ego'] = self.vehicle_a.vid
        req['config'] = vehicle_config
        log.info('Sending Non-Crash request: %s', req)
        self.bng.send(req)
        resp = self.bng.recv()
        assert resp['type'] == 'ImpactGenNonCrashRan'
        log.info('Non-crash finished.')

        return setting

    def run_incident(self, incident):
        log.info('Setting up next incident.')
        self.bng.display_gui_message('Setting up next incident...')
        setting = incident()
        self.capture_post(setting)
        return setting

    def run_incidents(self):
        log.info('Enumerating possible incidents.')
        count = 1

        incidents = [
            self.run_t_bone_crash,
            self.run_linear_crash,
            self.run_pole_crash,
            self.run_no_crash,
        ]

        while not self.settings_exhausted():
            log.info('Running incident %s of %s...', count,
                     self.total_possibilities)
            self.bng.restart_scenario()
            log.info('Scenario restarted.')
            time.sleep(5.0)
            self.vehicle_b.set_part_config(self.vehicle_b_config)
            log.info('Vehicle B config set.')

            incident = incidents[count % len(incidents)]
            if self.run_incident(incident) is None:
                log.info('Ran out of options for: %s', incident)
                incidents.remove(incident)  # Possibility space exhausted

            count += 1

    def run(self):
        log.info('Starting up BeamNG instance.')
        self.bng.open(['impactgen/crashOutput'])
        self.bng.skt.settimeout(1000)
        try:
            log.info('Setting up BeamNG instance.')
            self.setup()
            self.run_incidents()
        finally:
            log.info('Closing BeamNG instance.')
            self.bng.close()