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
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) print(b.velocity) print(b.angular_velocity) print("overlapping: ", intersect(c.hitbox(), b.hitbox()))
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(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
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