def estimate_time(car: Car, target, dd=1) -> float: turning_radius = 1 / Drive.max_turning_curvature(norm(car.velocity) + 500) turning = angle_between(car.forward() * dd, direction( car, target)) * turning_radius / 1800 if turning < 0.5: turning = 0 dist = ground_distance(car, target) - 200 if dist < 0: return turning speed = dot(car.velocity, car.forward()) time = 0 result = None if car.boost > 0 and dd > 0: boost_time = car.boost / 33.33 result = BOOST.simulate_until_limit(speed, distance_limit=dist, time_limit=boost_time) dist -= result.distance_traveled time += result.time_passed speed = result.speed_reached if dist > 0 and speed < 1410: result = THROTTLE.simulate_until_limit(speed, distance_limit=dist) dist -= result.distance_traveled time += result.time_passed speed = result.speed_reached if result is None or not result.distance_limit_reached: time += dist / speed return time * 1.05 + turning
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 __init__(self, car: Car, ball_predictions, predicate: callable = None): self.ball: Ball = None self.is_viable = True #find the first reachable ball slice that also meets the predicate test_car = Car(car) test_aerial = Aerial(car) for ball in ball_predictions: test_aerial.target = ball.position test_aerial.arrival_time = ball.time # fake our car state :D dir_to_target = ground_direction(test_car.position, test_aerial.target) test_car.velocity = dir_to_target * max(norm(test_car.velocity), 1200) test_car.orientation = look_at(dir_to_target, vec3(0,0,1)) if test_aerial.is_viable() and (predicate is None or predicate(car, ball)): self.ball = ball break #if no slice is found, use the last one if self.ball is None: self.ball = ball_predictions[-1] self.is_viable = False self.time = self.ball.time self.ground_pos = ground(self.ball.position) self.position = self.ball.position
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 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 estimate_time(car: Car, target, speed, dd=1) -> float: dist = distance(car, target) if dist < 100: return 0 travel = dist / speed turning = angle_between(car.forward() * dd, direction(car, target)) / math.pi * 2 if turning < 1: turning **= 2 acceleration = (speed * dd - dot(car.velocity, car.forward())) / 2100 * 0.2 * dd / max(car.boost / 20, 1) return travel + acceleration + turning * 0.7
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 toPoint(car: Car, target: vec3): direction = normalize(target - car.position) turnAngleAmount = math.acos( max(-1, min(1, dot(car.forward(), direction)))) rotateToZeroAngle = -atan2(direction) towardsZeroVector = rotate2(car.forward(), rotateToZeroAngle) turnDirection = math.copysign(1, -towardsZeroVector[1]) return PID.fromAngle(car, turnAngleAmount * turnDirection)
def best_intercept(self, cars: List[Car]) -> Intercept: if not cars: return Intercept(Car(), self.info.ball_predictions) intercepts = [ Intercept(car, self.info.ball_predictions) for car in cars ] return min(intercepts, key=lambda intercept: intercept.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 toPointReverse(car: Car, target: vec3): direction = normalize(target - car.position) turnAngleAmount = math.acos( max(-1, min(1, -dot(car.forward(), direction)))) rotateToZeroAngle = -atan2(direction) towardsZeroVector = rotate2(car.forward(), rotateToZeroAngle + math.pi) turnDirection = math.copysign(1, towardsZeroVector[1]) ANGLETERM = 3.5 ANGULARTERM = -0.3 angleP = turnDirection * min(2, ANGLETERM * turnAngleAmount) angleD = ANGULARTERM * car.angular_velocity[2] return min(1, max(-1, angleP + angleD))
def step(self, dt): if self.aerialing: self.aerial.target_orientation = look_at( direction(self.car, self.target), vec3(0, 0, -1)) self.aerial.step(dt) self.controls = self.aerial.controls self.finished = self.aerial.finished else: super().step(dt) # simulate aerial from current state simulated_car = self.simulate_flight(self.car) # if the car ended up too far, we're too fast and we need to slow down if ground_distance(self.car, self.aerial.target) + 100 < ground_distance( self.car, simulated_car): # self.controls.throttle = -1 pass # if it ended up near the target, we could take off elif distance(simulated_car, self.aerial.target) < self.MAX_DISTANCE_ERROR: if angle_to(self.car, self.aerial.target) < 0.1 or norm( self.car.velocity) < 1000: if self.DELAY_TAKEOFF: # extrapolate current state a small amount of time future_car = Car(self.car) time = 0.2 future_car.time += time displacement = future_car.velocity * time if norm(future_car.velocity) > 500\ else normalize(future_car.velocity) * 500 * time future_car.position += displacement # simulate aerial fot the extrapolated car again future_simulated_car = self.simulate_flight( future_car, write_to_flight_path=False) # if the aerial is also successful, that means we should continue driving instead of taking off # this makes sure that we go for the most late possible aerials, which are the most effective if distance( future_simulated_car, self.aerial.target) > self.MAX_DISTANCE_ERROR: self.aerialing = True else: self.too_early = True else: self.aerialing = True else: # self.controls.boost = True self.controls.throttle = 1
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
def align(car: Car, target: vec3, direction: vec3): turnAngleAmount = math.acos(dot(car.forward(), direction)) rotateToZeroAngle = -atan2(direction) posOffset = rotate2(target - car.position, rotateToZeroAngle) towardsZeroVector = rotate2(car.forward(), rotateToZeroAngle) turnDirection = math.copysign(1, -towardsZeroVector[1]) turnRadius = 1 / RLUDrive.max_turning_curvature(norm(car.velocity)) ANGLETERM = 10 ANGULARTERM = -0.3 CORRECTIONTERM = 1 / 40 angleP = turnDirection * min(2, ANGLETERM * turnAngleAmount) angleD = ANGULARTERM * car.angular_velocity[2] finalOffset = posOffset[1] + turnDirection * turnRadius / .85 * ( 1 - math.cos(turnAngleAmount)) offsetCorrection = CORRECTIONTERM * finalOffset return min(1, max(-1, angleP + angleD + offsetCorrection))
def reset_gamestate(self): print('> reset_gamestate()') # Initialize inputs self.reset_for_ground_shots() t = self.target b = Ball(self.game.ball) c = Car(self.game.cars[self.index]) b.location = to_vec3(self.initial_ball_location) b.velocity = to_vec3(self.initial_ball_velocity) c.location = to_vec3(self.initial_car_location) c.velocity = to_vec3(self.initial_car_velocity) # Point car at ball c.rotation = look_at( vec3(b.location[0] - c.location[0], b.location[1] - c.location[1], 0), vec3(0, 0, 1)) rotator = rotation_to_euler(c.rotation) # Reset self.aerial = None self.dodge = None self.rotation_input = None self.timer = 0.0 # Set gamestate car_state = CarState(boost_amount=100, physics=Physics( location=self.initial_car_location, velocity=self.initial_car_velocity, rotation=rotator, angular_velocity=Vector3(0, 0, 0))) ball_state = BallState( Physics(location=self.initial_ball_location, velocity=self.initial_ball_velocity, rotation=Rotator(0, 0, 0), angular_velocity=Vector3(0, 0, 0))) game_state = GameState(ball=ball_state, cars={self.index: car_state}) self.set_game_state(game_state)
def simulate(self, bot) -> vec3: # print('simulate intercept') # Init vars c = Car(bot.game.my_car) b = Ball(bot.game.ball) t = vec3(bot.target) intercept = self.location dt = 1.0 / 60.0 hit = False min_error = None # Drive towards intercept (moving in direction of c.forward()) c.rotation = look_at(intercept, c.up()) direction = normalize(intercept - c.location) #c.forward() advance_distance = norm(intercept - c.location) - c.hitbox( ).half_width[0] - b.collision_radius translation = direction * advance_distance sim_start_state: ThrottleFrame = BoostAnalysis().travel_distance( advance_distance, norm(c.velocity)) c.velocity = direction * sim_start_state.speed c.location += translation c.time += sim_start_state.time bot.ball_predictions = [vec3(b.location)] while b.time < c.time: b.step(dt) bot.ball_predictions.append(vec3(b.location)) # print(c.time, b.time) # print(c.location, b.location) # Simulate the collision and resulting for i in range(60 * 3): c.location += c.velocity * dt b.step(dt, c) # Check if we hit the ball yet if norm(b.location - c.location) < (c.hitbox().half_width[0] + b.collision_radius) * 1.05: hit = True # print('hit') # Measure dist from target error = t - b.location if hit and (min_error == None or norm(error) < norm(min_error)): min_error = error # Record trajectory bot.ball_predictions.append(vec3(b.location)) if not hit: return None return min_error
def best_intercept(self, cars, max_height=9999) -> Intercept: best_intercept = None best_car = None for car in cars: intercept = Intercept(car, self.info.ball_predictions, lambda car, ball: ball.position[2] < max_height) if best_intercept is None or intercept.time <= best_intercept.time: best_intercept = intercept best_car = car if best_intercept is None: best_car = Car() best_intercept = Intercept(best_car, []) return best_intercept, best_car
def get_controls(self, car_state: CarState, car: Car): controls = SimpleControllerState() target_Vec3 = Vec3(self.location[0], self.location[1], self.location[2]) if angle_between(self.location - to_vec3(car_state.physics.location), car.forward()) > pi / 2: controls.boost = False controls.handbrake = True elif angle_between(self.location - to_vec3(car_state.physics.location), car.forward()) > pi / 4: controls.boost = False controls.handbrake = False else: controls.boost = self.boost controls.handbrake = False # Be smart about not using boost at max speed # if Vec3(car.physics.velocity).length() > self.boost_analysis.frames[-1].speed - 10: # controls.boost = False controls.steer = steer_toward_target(car_state, target_Vec3) controls.throttle = 1 return controls
def __init__(self, car: Car, use_boost=False): self.car = car self.use_boost = use_boost self.controls = Input() self.dodge = Dodge(car) self.dodge.duration = 0.12 self.dodge.direction = vec2(car.forward() * (-1)) self.s = 0.95 * sgn( dot(self.car.angular_velocity, self.car.up()) + 0.01) self.timer = 0.0 self.finished = False
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 __init__(self, car: Car, target: vec3 = vec3(0, 0, 0), waste_boost=False): super().__init__(car) self.target = Arena.clamp(ground(target), 100) self.waste_boost = waste_boost self.finish_distance = 500 self._time_on_ground = 0 self.driving = True # decide whether to start driving backwards and halfflip later forward_estimate = estimate_time(car, self.target) backwards_estimate = estimate_time(car, self.target, -1) + 0.5 backwards = ( dot(car.velocity, car.forward()) < 500 and backwards_estimate < forward_estimate and (distance(car, self.target) > 3000 or distance(car, self.target) < 300) and car.position[2] < 200 ) self.drive = Drive(car, self.target, 2300, backwards) self.action = self.drive
def calculate(car: Car, ball: Ball, target: vec3, ball_predictions=None): # Init vars b = Ball(ball) dt = 1.0 / 60.0 # Generate predictions of ball path if ball_predictions is None: ball_predictions = [] for i in range(60 * 5): b.step(dt) ball_predictions.append(vec3(b.location)) # Gradually converge on ball location by aiming at a location, checking time to that location, # and then aiming at the ball's NEW position. Guaranteed to converge (typically in <10 iterations) # unless the ball is moving away from the car faster than the car's max boost speed intercept = Intercept(b.location) intercept.purpose = 'ball' intercept.boost = True intercept_ball_position = vec3(b.location) collision_achieved = False last_horizontal_error = None last_horizontal_offset = None i = 0 max_tries = 101 analyzer = BoostAnalysis() if intercept.boost else ThrottleAnalysis() while i < max_tries: i += 1 fake_car = Car(car) direction = normalize(intercept.location - car.location) fake_car.rotation = look_at(direction, fake_car.up()) for t in range(60 * 5): # Step car location with throttle/boost analysis data # Not super efficient but POITROAE frame = analyzer.travel_time(dt, norm(fake_car.velocity)) # print('in 1 frame I travel', frame.time, frame.distance, frame.speed) fake_car.location += direction * frame.distance fake_car.velocity = direction * frame.speed fake_car.time += dt ball_location = ball_predictions[t] # Check for collision p = closest_point_on_obb(fake_car.hitbox(), ball_location) if norm(p - ball_location) <= ball.collision_radius: direction_vector = p - (fake_car.location - normalize( fake_car.forward()) * 13.88) # octane center of mass direction_vector[2] = 0 target_direction_vector = target - ball_location target_direction_vector[2] = 0 intercept_ball_position = ball_location direction = atan2(direction_vector[1], direction_vector[0]) ideal_direction = atan2(target_direction_vector[1], target_direction_vector[0]) horizontal_error = direction - ideal_direction # intercept.location = vec3(ball_location) # intercept.time = fake_car.time # return intercept # Now descend the hit direction gradient # Kick off the gradient descent with an arbitrary seed value if last_horizontal_error is None: last_horizontal_error = horizontal_error last_horizontal_offset = 0 if horizontal_error > 0: horizontal_offset = 25 else: horizontal_offset = 25 intercept.location = ball_location - normalize( fake_car.left()) * horizontal_offset break # Recursive case of gradient descent if horizontal_offset == last_horizontal_offset: gradient = 0 else: gradient = (horizontal_error - last_horizontal_error ) / (horizontal_offset - last_horizontal_offset) if gradient == 0: predicted_horizontal_offset = horizontal_offset else: predicted_horizontal_offset = horizontal_offset - horizontal_error / gradient # Base case (convergence) if abs(gradient) < 0.0005: print(f'convergence in {i} iterations') print(f'gradient = {gradient}') print( f'last_horizontal_offset = {last_horizontal_offset}' ) print(f'direction = {degrees(direction)}') print(f'ideal direction = {degrees(ideal_direction)}') print(f'target = {target}') print(f'ball_location = {ball_location}') return intercept # Edge case exit: offset maxed out max_horizontal_offset = car.hitbox( ).half_width[1] + ball.collision_radius if predicted_horizontal_offset > max_horizontal_offset: predicted_horizontal_offset = max_horizontal_offset elif predicted_horizontal_offset < -max_horizontal_offset: predicted_horizontal_offset = -max_horizontal_offset last_horizontal_offset = horizontal_offset last_horizontal_error = horizontal_error horizontal_offset = predicted_horizontal_offset # Return the latest intercept location and continue descending the gradient intercept.location = ball_location - normalize( fake_car.left()) * predicted_horizontal_offset print(f'iteration {i}') print(f'gradient = {gradient}') print(f'horizontal_offset = {horizontal_offset}') print(f'horizontal_error = {degrees(horizontal_error)}') # print(f'ideal direction = {degrees(ideal_direction)}') break # Check for arrival if norm(fake_car.location - intercept.location) < ball.collision_radius / 2: intercept.location = ball_location break if i >= max_tries: print( f'Warning: max tries ({max_tries}) exceeded for calculating intercept' ) return intercept
from rlutilities.simulation import Car, Navigator from rlutilities.mechanics import FollowPath from rlutilities.linear_algebra import vec3, normalize c = Car() c.time = 0.0 c.position = vec3(0, 0, 0) c.velocity = vec3(1000, 0, 0) c.angular_velocity = vec3(0.1, -2.0, 1.2) c.on_ground = False navigator = Navigator(c) drive = FollowPath(c) drive.arrival_speed = 1234 drive.path = navigator.path_to(vec3(1000, 0, 0), vec3(1, 0, 0), 1000) for p in drive.path.points: print(p)
from rlutilities.linear_algebra import vec3, mat3 from rlutilities.simulation import Game, Car, Ball, intersect Game.set_mode("dropshot") c = Car() c.location = vec3(-164.13, 0, 88.79) c.velocity = vec3(1835.87, 0, 372.271) c.angular_velocity = vec3(0, 3.78721, 0) c.rotation = mat3(0.9983, -5.23521e-6, 0.0582877, 5.5498e-6, 1.0, -5.23521e-6, -0.0582877, 5.5498e-6, 0.9983) b = Ball() b.location = vec3(0, 0, 150) b.velocity = vec3(0, 0, 0) b.angular_velocity = vec3(0, 0, 0) print("before:") print(b.location) print(b.velocity) print(b.angular_velocity) print("overlapping: ", intersect(c.hitbox(), b.hitbox())) print() b.step(0.008333, c) print("after:") print(b.location)
def get_output(self, packet: GameTickPacket) -> SimpleControllerState: # Record start time self.tick_start = time.time() # Gather some information about our car and the ball my_car: CarState = packet.game_cars[self.index] car_location = Vec3(my_car.physics.location) car_velocity = Vec3(my_car.physics.velocity) car_direction = car_velocity.ang_to(Vec3( 1, 0, 0)) if car_velocity.length() > 0 else 0 ball_location = Vec3(packet.game_ball.physics.location) ball_velocity = Vec3(packet.game_ball.physics.velocity) ball_direction = ball_velocity.ang_to(Vec3( 1, 0, 0)) if ball_velocity.length() > 0 else 0 reset = False # Initialize simulation game model if self.game == None: Game.set_mode('soccar') self.game = Game(self.index, self.team) self.game.read_game_information(packet, self.get_rigid_body_tick(), self.get_field_info()) self.target = vec3( 0, 5120 + 880 / 2 if self.team is 0 else -(5120 + 880 / 2), 642.775 / 2) # Opposing net self.reset_gamestate() print('TEAM', self.team) return SimpleControllerState() # Update simulation self.game.read_game_information(packet, self.get_rigid_body_tick(), self.get_field_info()) # Check for car hit ball if self.last_touch_location != packet.game_ball.latest_touch.hit_location: self.last_touch_location = Vec3( packet.game_ball.latest_touch.hit_location) print(f'> Car hit ball') self.not_hit_yet = False # Recalculate intercept every frame self.plan() # Re-simulate the aerial every frame if self.aerial is not None and not self.aerial.finished: simulate_aerial(self) simulate_alternate_aerials(self) # Update dodge (init or clean up old) # try_init_dodge(self) # Rendering if len(self.ball_predictions) > 2: self.renderer.draw_polyline_3d(self.ball_predictions, self.renderer.red()) if self.aerial != None and self.aerial.target: self.renderer.draw_rect_3d(self.aerial.target, 8, 8, True, self.renderer.green(), centered=True) self.renderer.draw_line_3d(car_location, self.aerial.target, self.renderer.white()) self.renderer.draw_line_3d(vec3_to_Vec3(self.target), self.target + self.avg_aerial_error, self.renderer.cyan()) if self.intercept != None: self.renderer.draw_rect_3d(self.intercept.location, 8, 8, True, self.renderer.green(), centered=True) self.renderer.draw_rect_3d(vec3_to_Vec3(self.target), 8, 8, True, self.renderer.green(), centered=True) # Controller state if reset: self.reset_gamestate() return SimpleControllerState() # "Do a flip!" elif self.dodge is not None: if self.dodge.finished: self.dodge = None return SimpleControllerState() self.dodge.step(self.game.time_delta) return self.dodge.controls # Just do an aerial :4head: elif self.aerial is not None: aerial_step(self.aerial, Car(self.game.my_car), self.rotation_input, self.game.time_delta) return self.aerial.controls # Just hit the ball :4head: elif self.intercept is not None: if self.intercept.dodge and abs(self.game.time - self.intercept. jump_time) <= self.game.time_delta: print('im gonna nut') self.dodge = Dodge(self.game.my_car) self.dodge.duration = 0.2 self.dodge.preorientation = self.intercept.dodge_preorientation self.dodge.delay = self.intercept.dodge_delay + 0.1 self.dodge.direction = self.intercept.dodge_direction self.dodge.step(self.game.time_delta) return self.dodge.controls return self.intercept.get_controls( my_car, self.game.my_car ) #drive_at(self, my_car, self.intercept.location) return SimpleControllerState()
def step(self, dt): time_left = self.aerial.arrival_time - self.car.time if self.aerialing: to_ball = direction(self.car, self.info.ball) # freestyling if self.car.position[2] > 200: # if time_left > 0.7: # rotation = axis_to_rotation(self.car.forward() * 0.5) # self.aerial.up = dot(rotation, self.car.up()) # else: self.aerial.up = vec3(0, 0, -1) + xy(to_ball) self.aerial.target_orientation = look_at(to_ball, vec3(0, 0, -3) + to_ball) self.aerial.step(dt) self.controls = self.aerial.controls self.finished = self.aerial.finished and time_left < -0.3 else: super().step(dt) # simulate aerial from current state simulated_car = self.simulate_flight(self.car, self.aerial, self._flight_path) speed_towards_target = dot( self.car.velocity, ground_direction(self.car, self.aerial.target_position)) speed_needed = ground_distance( self.car, self.aerial.target_position) / time_left # too fast, slow down if speed_towards_target > speed_needed and angle_to( self.car, self.aerial.target_position) < 0.1: self.controls.throttle = -1 # if it ended up near the target, we could take off elif distance( simulated_car, self.aerial.target_position) < self.MAX_DISTANCE_ERROR: if angle_to(self.car, self.aerial.target_position) < 0.1 or norm( self.car.velocity) < 1000: if self.DELAY_TAKEOFF and ground_distance( self.car, self.aerial.target_position) > 1000: # extrapolate current state a small amount of time future_car = Car(self.car) time = 0.5 future_car.time += time displacement = future_car.velocity * time if norm(future_car.velocity) > 500\ else normalize(future_car.velocity) * 500 * time future_car.position += displacement # simulate aerial fot the extrapolated car again future_simulated_car = self.simulate_flight( future_car, self.aerial) # if the aerial is also successful, that means we should continue driving instead of taking off # this makes sure that we go for the most late possible aerials, which are the most effective if distance(future_simulated_car, self.aerial. target_position) > self.MAX_DISTANCE_ERROR: self.aerialing = True else: self.too_early = True else: self.aerialing = True else: # self.controls.boost = True self.controls.throttle = 1
def get_car_front_center(car: Car): return car.location + normalize(car.forward()) * car.hitbox( ).half_width[0] + normalize(car.up()) * car.hitbox().half_width[2]
def calculate_old(car: Car, ball: Ball, target: vec3, ball_predictions=None): # Init vars fake_car = Car(car) b = Ball(ball) # Generate predictions of ball path if ball_predictions is None: ball_predictions = [vec3(b.location)] for i in range(60 * 5): b.step(1.0 / 60.0) ball_predictions.append(vec3(b.location)) # Gradually converge on ball location by aiming at a location, checking time to that location, # and then aiming at the ball's NEW position. Guaranteed to converge (typically in <10 iterations) # unless the ball is moving away from the car faster than the car's max boost speed intercept = Intercept(b.location) intercept.purpose = 'ball' intercept.boost = True intercept_ball_position = vec3(b.location) i = 0 max_tries = 100 analyzer = BoostAnalysis() if intercept.boost else ThrottleAnalysis() while i < max_tries: # Find optimal spot to hit the ball optimal_hit_vector = normalize( target - intercept_ball_position) * b.collision_radius optimal_hit_location = intercept_ball_position - optimal_hit_vector # Find ideal rotation, unless it intersects with ground optimal_rotation = look_at( optimal_hit_vector, vec3(0, 0, 1) ) #axis_to_rotation(optimal_hit_vector) # this might be wrong fake_car.rotation = optimal_rotation # print(f'fake_car.location {fake_car.location}') # print(f'get_car_front_center(fake_car) {get_car_front_center(fake_car)}') fake_car.location += optimal_hit_location - get_car_front_center( fake_car ) # try to position the car's front center directly on top of the best hit vector euler = rotation_to_euler(optimal_rotation) # todo put some super precise trigonometry in here to find the max angle allowed at given height if fake_car.location[2] <= fake_car.hitbox().half_width[0]: euler.pitch = 0 fake_car.rotation = euler_to_rotation( vec3(euler.pitch, euler.yaw, euler.roll)) fake_car.location += optimal_hit_location - get_car_front_center( fake_car ) # try to position the car's front center directly on top of the best hit vector # Adjust vertical position if it (still) intersects with ground if fake_car.location[2] < 17.0: fake_car.location[2] = 17.0 intercept.location = get_car_front_center(fake_car) # Calculate jump time needed jump_height_time = JumpAnalysis().get_frame_by_height( intercept.location[2]).time # or solve with motion equation # car_euler = rotation_to_euler(car.rotation) # jump_pitch_time = (euler.pitch - car_euler.pitch) / 5.5 + 0.35 # disregarding angular acceleration # jump_yaw_time = (euler.yaw - car_euler.yaw) / 5.5 + 0.35 # disregarding angular acceleration # jump_roll_time = (euler.roll - car_euler.roll) / 5.5 + 0.35 # disregarding angular acceleration # jump_time = max(jump_height_time, jump_pitch_time, jump_yaw_time, jump_roll_time) jump_time = jump_height_time # todo revisit rotation time # print('jump_time', jump_time) # Calculate distance to drive before jumping (to arrive perfectly on target) total_translation = intercept.location - get_car_front_center(car) total_translation[2] = 0 total_distance = norm(total_translation) start_index = analyzer.get_index_by_speed(norm(car.velocity)) start_frame = analyzer.frames[start_index] custom_error_func = lambda frame: abs(total_distance - ( frame.distance - start_frame.distance) - frame.speed * jump_time) drive_analysis = analyzer.get_frame_by_error( custom_error_func, start_index) arrival_time = drive_analysis.time - start_frame.time + jump_time # print('drive_analysis.time', drive_analysis.time) # print('drive_analysis', start_index) # arrival_time = analyzer.travel_distance(total_distance, norm(car.velocity)).time # drive_analysis = analyzer.travel_distance(norm(intercept.location - c.location), norm(c.velocity)) ball_index = int(round(arrival_time * 60)) if ball_index >= len(ball_predictions): intercept.location = ball_predictions[-1] intercept.time = len(ball_predictions) / 60.0 break ball_location = ball_predictions[ball_index] # print(f'Iteration {i} distance {norm(ball_location + vec3(optimal_hit_vector[0], optimal_hit_vector[1], 0) - intercept.location)}') if norm(ball_location - intercept_ball_position) <= 1: # if norm(intercept_ball_position - get_car_front_center(fake_car)) > 100: # intercept.location = ball_predictions[-1] # intercept.time = len(ball_predictions) / 60.0 # return intercept intercept.dodge = True #jump_time > 0.2 intercept.jump_time = car.time + arrival_time - jump_time intercept.dodge_preorientation = euler_to_rotation( vec3(euler.pitch, euler.yaw, euler.roll)) intercept.dodge_delay = jump_time intercept.dodge_direction = normalize(vec2(optimal_hit_vector)) # print(f'intercept_ball_position', intercept_ball_position) # print(f'intercept.location', intercept.location) # print(f'time until jump {drive_analysis.time}') # print(f'time now {car.time}') # print(f'distance until jump {drive_analysis.distance}') # print(f'total distance to target {total_distance}') # print(f'horiz speed @ jump {drive_analysis.speed}') # print(f'time intended to be in air {jump_time}') # print(f'distance travelled in air {jump_time * drive_analysis.speed}') # print(f'distance remaining to target @ jump {total_distance - drive_analysis.distance}') # print(f'Intercept convergence in {i} iterations') # print(f'desired roll {euler.roll}') # print(f'actual roll {rotation_to_euler(c.rotation).roll}') break intercept_ball_position = vec3(ball_location) # intercept.location = vec3(ball_location) # intercept.location[2] = 0 intercept.time = arrival_time i += 1 if i >= max_tries: print( f'Warning: max tries ({max_tries}) exceeded for calculating intercept' ) # Intercept is only meant for ground paths (and walls/cieling are only indirectly supported) # collision_radius = c.hitbox().half_width[2] * 2 + b.collision_radius + b.collision_radius * 8 # on_ground = intercept.location[2] <= collision_radius # on_back_wall = abs(intercept.location[1]) >= 5120 - collision_radius # on_side_wall = abs(intercept.location[0]) >= 4096 - collision_radius # # on_cieling = intercept.location[2] >= 2044 - collision_radius # reachable = on_ground # or on_back_wall or on_side_wall # or on_cieling # if not reachable: # return None return intercept