def simulate_landing(self): dummy = Car(self.car) self.trajectory = [vec3(dummy.position)] self.landing = False collision_normal: Optional[vec3] = None dt = 1 / 60 simulation_duration = 0.8 for i in range(int(simulation_duration / dt)): dummy.step(Input(), dt) self.trajectory.append(vec3(dummy.position)) collision_sphere = sphere(dummy.position, 50) collision_ray = Field.collide(collision_sphere) collision_normal = collision_ray.direction if (norm(collision_normal) > 0.0 or dummy.position[2] < 0) and i > 20: self.landing = True self.landing_pos = dummy.position break if self.landing: u = collision_normal f = normalize(dummy.velocity - dot(dummy.velocity, u) * u) l = normalize(cross(u, f)) self.aerial_turn.target = mat3(f[0], l[0], u[0], f[1], l[1], u[1], f[2], l[2], u[2]) else: target_direction = normalize( normalize(self.car.velocity) - vec3(0, 0, 3)) self.aerial_turn.target = look_at(target_direction, vec3(0, 0, 1))
def simulate(self): ball_prediction = self.get_ball_prediction_struct() duration_estimate = math.floor( get_time_at_height(self.game.ball.location[2], 0.2) * 10) / 10 for i in range(6): car = Car(self.game.my_car) ball = Ball(self.game.ball) batmobile = obb() batmobile.half_width = vec3(64.4098892211914, 42.335182189941406, 14.697200775146484) batmobile.center = car.location + dot(car.rotation, vec3(9.01, 0, 12.09)) batmobile.orientation = car.rotation dodge = Dodge(car) dodge.duration = duration_estimate + i / 60 dodge.target = ball.location for j in range(round(60 * dodge.duration)): dodge.target = ball.location dodge.step(1 / 60) car.step(dodge.controls, 1 / 60) prediction_slice = ball_prediction.slices[j] physics = prediction_slice.physics ball_location = vec3(physics.location.x, physics.location.y, physics.location.z) dodge.target = ball_location batmobile.center = car.location + dot(car.rotation, vec3(9.01, 0, 12.09)) batmobile.orientation = car.rotation if intersect(sphere(ball_location, 93.15), batmobile) and abs( ball_location[2] - car.location[2] ) < 25 and car.location[2] < ball_location[2]: return True, j / 60, ball_location return False, None, None
def car_trajectory(self, car: Car, end_time: float, dt: float = 1 / 10): steps = [] test_car = Car(car) while test_car.time < end_time: dt = min(dt, end_time - test_car.time) test_car.step(Input(), dt) test_car.time += dt steps.append(vec3(test_car.position)) self.polyline(steps)
def dodge_simulation(end_condition=None, car=None, hitbox_class=None, dodge=None, ball=None, game_info=None, boost=True): ''' Simulates an RLU dodge until the dodge ends, or one of pass_condition or fail_condtion are met. pass_condition means that the dodge does what we wanted. Returns True and the RLU car state at the end fail_condition returns (False, None), meaning the dodge doesn't achieve the desired result. ''' #Copy everything we need and set constants time = 0 dt = 1 / 60 car_copy = RLU_Car(car) dodge_copy = RLU_Dodge(car_copy) if dodge.target != None: dodge_copy.target = dodge.target if dodge.direction != None: dodge_copy.direction = dodge.direction if dodge.preorientation != None: dodge_copy.preorientation = dodge.preorientation if dodge.duration != None: dodge_copy.duration = dodge.duration else: dodge_copy.duration = 0 #Make sure there's time between the jump and the dodge so that we don't just keep holding jump if dodge.delay != None: dodge_copy.delay = dodge.delay else: dodge_copy.delay = max(dodge_copy.duration + 2 * dt, 0.05) #Adjust for non-octane hitboxes box = update_hitbox(car_copy, hitbox_class) #Loop until we hit end_condition or the dodge is over. while not end_condition(time, box, ball, game_info.team_sign): #Update simulations and adjust hitbox again time += dt dodge_copy.step(dt) controls = dodge_copy.controls if boost: controls.boost = 1 car_copy.step(controls, dt) box = update_hitbox(car_copy, hitbox_class) if dodge_copy.finished: #If the dodge never triggers condition, give up and move on #TODO: give up sooner to save computation time return Simulation() return Simulation(ball_contact=True, car=car_copy, box=box, time=time)
def moving_ball_dodge_contact(game_info): ''' Returns dodge duration and delay so the car can reach contact_height ''' ball = game_info.ball contact_height = ball.pos.z - 20 hitbox_class = game_info.me.hitbox_class car_copy = RLU_Car(game_info.utils_game.my_car) turn = RLU_AerialTurn(car_copy) turn.target = roll_away_from_target(ball.pos, pi / 4, game_info) box = update_hitbox(car_copy, hitbox_class) time = 0 dt = 1 / 60 ball_contact = has_ball_contact(time, box, ball, game_info.team_sign) closest_point = ball_contact[1] while closest_point[2] < contact_height and not ball_contact[0]: time += dt ball = game_info.ball_prediction.state_at_time(game_info.game_time + time) turn.step(dt) contact_height = ball.pos.z - 30 controls = turn.controls if time <= 0.20: controls.jump = 1 controls.boost = 1 car_copy.step(controls, dt) box = update_hitbox(car_copy, hitbox_class) ball_contact = has_ball_contact(time, box, ball, game_info.team_sign) closest_point = ball_contact[1] if time >= 1.45: #Max dodge time return None, None, Simulation() if not ball_contact[0]: return None, None, Simulation() if time < 0.2: duration = time delay = duration + 2 * dt else: duration = 0.2 delay = time delay -= 0.05 #Window to dodge just before ball contact return duration, delay, Simulation(ball_contact=True, car=car_copy, hitbox=box, time=time)
def stationary_ball_dodge_contact(game_info, contact_height): ''' Returns dodge duration and delay so the car can reach contact_height ''' ball = game_info.ball hitbox_class = game_info.me.hitbox_class car_copy = RLU_Car(game_info.utils_game.my_car) turn = RLU_AerialTurn(car_copy) turn.target = roll_away_from_target(ball.pos, pi / 4, game_info) box = update_hitbox(car_copy, hitbox_class) time = 0 dt = 1 / 60 ball_contact = has_ball_contact(time, box, ball, game_info.team_sign) intended_contact_point = ball_contact[1] while intended_contact_point[2] < contact_height and not ball_contact[0]: time += dt turn.step(dt) controls = turn.controls if time <= 0.20: controls.jump = 1 controls.boost = 1 car_copy.step(controls, dt) box = update_hitbox(car_copy, hitbox_class) ball_contact = has_ball_contact(time, box, ball, game_info.team_sign) intended_contact_point = ball_contact[1] if time >= 1.45: #Max dodge time return None, None, Simulation() if not ball_contact[0]: return None, None, Simulation() if time < 0.2: duration = time delay = duration + 2 * dt else: duration = 0.2 delay = time delay -= 0.05 #How long before we hit the ball is acceptable to dodge return duration, delay, Simulation(ball_contact=True, car=car_copy, hitbox=box, time=time)
def simulate_flight(car: Car, aerial: Aerial, flight_path: List[vec3] = None) -> Car: test_car = Car(car) test_aerial = Aerial(test_car) test_aerial.target = aerial.target test_aerial.arrival_time = aerial.arrival_time test_aerial.angle_threshold = aerial.angle_threshold test_aerial.up = aerial.up test_aerial.single_jump = aerial.single_jump if flight_path: flight_path.clear() while not test_aerial.finished: test_aerial.step(1 / 120) test_car.boost = 100 # TODO: fix boost depletion in RLU car sim test_car.step(test_aerial.controls, 1 / 120) if flight_path: flight_path.append(vec3(test_car.position)) return test_car
def simulate_flight(self, car: Car, write_to_flight_path=True) -> Car: test_car = Car(car) test_aerial = Aerial(test_car) test_aerial.target = self.aerial.target test_aerial.arrival_time = self.aerial.arrival_time test_aerial.angle_threshold = self.aerial.angle_threshold test_aerial.up = self.aerial.up test_aerial.single_jump = self.aerial.single_jump if write_to_flight_path: self._flight_path.clear() while not test_aerial.finished: test_aerial.step(1 / 120) test_car.boost = 100 # TODO: fix boost depletion in RLU car sim test_car.step(test_aerial.controls, 1 / 120) if write_to_flight_path: self._flight_path.append(vec3(test_car.position)) return test_car
c.time = 0.0 c.location = vec3(1509.38, -686.19, 17.01) c.velocity = vec3(-183.501, 1398., 8.321) c.angular_velocity = vec3(0, 0, 0) c.rotation = mat3(-0.130158, -0.991493, -0.00117062, 0.991447, -0.130163, 0.00948812, -0.00955977, 0.0000743404, 0.999954) c.dodge_rotation = mat2(-0.130163, -0.991493, 0.991493, -0.130163) c.on_ground = True c.jumped = False c.double_jumped = False c.jump_timer = -1.0 c.dodge_timer = -1.0 dodge = Dodge(c) dodge.direction = vec2(-230.03, 463.42) dodge.duration = 0.1333 dodge.delay = 0.35 f = open("dodge_simulation.csv", "w") for i in range(300): dodge.step(0.008333) print(c.time, dodge.controls.jump, dodge.controls.pitch, dodge.controls.yaw) c.step(dodge.controls, 0.008333) f.write( f"{c.time}, {c.location[0]}, {c.location[1]}, {c.location[2]}, " f"{c.velocity[0]}, {c.velocity[1]}, {c.velocity[2]}, " f"{c.angular_velocity[0]}, {c.angular_velocity[1]}, {c.angular_velocity[2]}\n" )
def simulate(self, global_target=None): lol = 0 # Initialize the ball prediction # Estimate the probable duration of the jump and round it down to the floor decimal ball_prediction = self.get_ball_prediction_struct() if self.info.my_car.boost < 6: duration_estimate = math.floor( get_time_at_height(self.info.ball.position[2]) * 10) / 10 else: adjacent = norm( vec2(self.info.my_car.position - self.info.ball.position)) opposite = (self.info.ball.position[2] - self.info.my_car.position[2]) theta = math.atan(opposite / adjacent) t = get_time_at_height_boost(self.info.ball.position[2], theta, self.info.my_car.boost) duration_estimate = (math.ceil(t * 10) / 10) # Loop for 6 frames meaning adding 0.1 to the estimated duration. Keeps the time constraint under 0.3s for i in range(6): # Copy the car object and reset the values for the hitbox car = Car(self.info.my_car) # Create a dodge object on the copied car object # Direction is from the ball to the enemy goal # Duration is estimated duration plus the time added by the for loop # preorientation is the rotation matrix from the ball to the goal # TODO make it work on both sides # Test with preorientation. Currently it still picks a low duration at a later time meaning it # wont do any of the preorientation. dodge = Dodge(car) prediction_slice = ball_prediction.slices[round( 60 * (duration_estimate + i / 60))] physics = prediction_slice.physics ball_location = vec3(physics.location.x, physics.location.y, physics.location.z) # ball_location = vec3(0, ball_y, ball_z) dodge.duration = duration_estimate + i / 60 if dodge.duration > 1.4: break if global_target is not None: dodge.direction = vec2(global_target - ball_location) target = vec3(vec2(global_target)) + vec3( 0, 0, jeroens_magic_number * ball_location[2]) dodge.preorientation = look_at(target - ball_location, vec3(0, 0, 1)) else: dodge.target = ball_location dodge.direction = vec2(ball_location) + vec2(ball_location - car.position) dodge.preorientation = look_at(ball_location, vec3(0, 0, 1)) # Loop from now till the end of the duration fps = 30 for j in range(round(fps * dodge.duration)): lol = lol + 1 # Get the ball prediction slice at this time and convert the location to RLU vec3 prediction_slice = ball_prediction.slices[round(60 * j / fps)] physics = prediction_slice.physics ball_location = vec3(physics.location.x, physics.location.y, physics.location.z) dodge.step(1 / fps) T = dodge.duration - dodge.timer if T > 0: if dodge.timer < 0.2: dodge.controls.boost = 1 dodge.controls.pitch = 1 else: xf = car.position + 0.5 * T * T * vec3( 0, 0, -650) + T * car.velocity delta_x = ball_location - xf if angle_between(vec2(car.forward()), dodge.direction) < 0.3: if norm(delta_x) > 50: dodge.controls.boost = 1 dodge.controls.throttle = 0.0 else: dodge.controls.boost = 0 dodge.controls.throttle = clip( 0.5 * (200 / 3) * T * T, 0.0, 1.0) else: dodge.controls.boost = 0 dodge.controls.throttle = 0.0 else: dodge.controls.boost = 0 car.step(dodge.controls, 1 / fps) succesfull = self.dodge_succesfull(car, ball_location, dodge) if succesfull is not None: if succesfull: return True, j / fps, ball_location else: break return False, None, None
class CarSimmer: def __init__(self, physics: SimPhysics): self.physics: SimPhysics = physics self.boost = 100 # Right now always a 100 self.renderer = None self.rlu_car = RLUCar() self.is_rlu_updated = False self.last_base = Vec3(0, 0, -1000) self.last_normal = Vec3(0, 0, 1) self.last_was_jump = False self.airtime = 0 self.count = 0 self.index = 0 self.locations = [] self.up = [] self.forward = [] def reset(self, physics: SimPhysics): self.physics = physics self._set_rlu_car() def is_jumped(self): return self.is_rlu_updated and self.rlu_car.jumped def is_double_jumped(self): return self.is_rlu_updated and self.rlu_car.double_jumped def is_on_ground(self): return not self.is_rlu_updated def is_supersonic(self): return self.physics.velocity.length() > 2200 def _set_rlu_car(self): c = self.rlu_car c.position = to_rlu_vec(self.physics.location) c.velocity = to_rlu_vec(self.physics.velocity) c.angular_velocity = to_rlu_vec(self.physics.angular_velocity) c.orientation = Orientation(self.physics.rotation).to_rot_mat() c.boost = self.boost c.jumped = False c.double_jumped = False c.on_ground = True c.team = 0 c.time = 0 self.is_rlu_updated = True def _make_input(self, controls): inputs = RLUInput() fields = [ 'boost', 'handbrake', 'jump', 'pitch', 'roll', 'steer', 'throttle', 'yaw' ] for f in fields: setattr(inputs, f, getattr(controls, f)) return inputs def _rlu_step(self, controls, dt, on_ground): # we think RLU sims this situation better # print(f"{self.count}: rlusim") if not self.is_rlu_updated: self._set_rlu_car() self.rlu_car.on_ground = on_ground inputs = self._make_input(controls) self.rlu_car.step(inputs, dt) self.physics.location = rlu_to_Vec3(self.rlu_car.position) self.physics.velocity = rlu_to_Vec3(self.rlu_car.velocity) self.physics.angular_velocity = rlu_to_Vec3( self.rlu_car.angular_velocity) self.physics.rotation = Orientation.from_rot_mat( self.rlu_car.orientation) # self.boost = self.rlu_car.boost def mark_location(self): N = 200 rate = 30 if not self.renderer: return # use as a ring buffer, list has N items # self.index marks the spot between latest and oldest if self.count % rate != 0: self.index = (self.index + 1) % N o = Orientation(self.physics.rotation) location = Vec3(self.physics.location) if len(self.locations) <= self.index: self.locations.append(location) self.up.append(o.up) self.forward.append(o.forward) else: self.locations[self.index] = location self.up[self.index] = o.up self.forward[self.index] = o.forward self.renderer.begin_rendering() end = len(self.locations) for index in range(self.index + 1, self.index + end): i = index % end loc = self.locations[i] component = float(i) / len(self.locations) inverse = 1 - component color = self.renderer.create_color(255, ceil(255 * component), ceil(255 * inverse / 2), ceil(255 * inverse)) self.renderer.draw_rect_3d(loc, 4, 4, True, color, centered=True) self.renderer.draw_line_3d(loc, loc + (self.up[i] * 200), color) self.renderer.end_rendering() def tick(self, controls: SimpleControllerState, dt: float): self.count += 1 self.mark_location() was_ground = self.is_on_ground() if not was_ground: self.airtime += dt else: self.airtime = 0 # we will now find actual normal result = Field.collide(make_obb(self.physics)) normal = rlu_to_Vec3(result.direction) on_ground = True if normal.length() < 0.1: # normally should be one. This just means there is no collision last_distance = (self.last_base - self.physics.location).length() if last_distance > (36.16 / 2) * 1.01: on_ground = False self.last_base.z = 1e5 normal = self.last_normal else: self.last_base = clamp_location(rlu_to_Vec3(result.start)) self.last_normal = normal if not on_ground or controls.jump: self._rlu_step(controls, dt, on_ground and not self.last_was_jump) self.last_was_jump = controls.jump clamp(self.physics) return self.physics # TODO: Special landing logic to maintain speed if on_ground and self.airtime > 0: # we are landing! lets give ourselves a nice landing # NOT REALISTIC but that's not our goal anyways # we want up = normal, and maintain all momentum in the appropriate direction orientation = Orientation(self.physics.rotation) self.physics.rotation = rotate_by_axis(orientation, orientation.up, normal) orientation = Orientation(self.physics.rotation) # now lets update the velocities remove_velocity_against_normal(self.physics.velocity, normal) self.last_was_jump = controls.jump self.is_rlu_updated = False orientation = Orientation(self.physics.rotation) # compare orientations if normal.ang_to(orientation.up) > pi / 6: # 30 degrees seems like an awful a lot return stuck_on_ground(self.physics, controls, dt, result, self.renderer) self.physics.rotation = rotate_by_axis(orientation, orientation.up, normal) self.physics.angular_velocity = Vec3(0, 0, 0) rotate_ground_and_move(self.physics, controls, dt, normal) clamp(self.physics) return self.physics