def update_vertex_list(self): offset_angle = (self.offset2 - self.offset1).direction p1_to_back = -self.offset1 p1_to_back.direction -= offset_angle back_distance = p1_to_back.y back_offset_vector = Vector2D.from_polar(back_distance, offset_angle + pi / 2) front_distance = -copysign(PocketRenderer.FRONT_DISTANCE, back_distance) front_offset_vector = Vector2D.from_polar(front_distance, offset_angle + pi / 2) points = [ self.offset1 + back_offset_vector + self.position, self.offset2 + back_offset_vector + self.position, self.offset2 + front_offset_vector + self.position, self.offset1 + front_offset_vector + self.position, ] self._vertex_list.vertices[:] = [number for point in points for number in point] self._vertex_list.colors[:] = self.color * 4
def __init__(self, target, actor_ball, balls): """ :type target: ShotTarget :type actor_ball: Ball :type balls: BallGroup """ self._position = actor_ball.position # get a pair of vectors pointing at the pocket v1 = target.point1 - actor_ball.position v2 = target.point2 - actor_ball.position # find ball radius offsets if (abs(v2.direction - (v1.direction + pi / 2)) < abs(v2.direction - (v1.direction - pi / 2))): sign = 1 else: sign = -1 off1 = Vector2D.from_polar(Ball.RADIUS * 2, v1.direction - sign * pi / 2) off2 = Vector2D.from_polar(Ball.RADIUS * 2, v2.direction + sign * pi / 2) # derive a system of inequalities from the vectors and offsets p1 = actor_ball.position + off1 p2 = actor_ball.position + off2 v1quad = v1.direction.quadrant v2quad = v2.direction.quadrant hem = None def get_east_west_cmp(): if v1.normalized().y < v2.normalized().y: return 1, -1 else: return -1, 1 if v1quad in Hemisphere.EAST and v2quad in Hemisphere.EAST: hem = Hemisphere.EAST cmp1, cmp2 = get_east_west_cmp() elif v1quad in Hemisphere.WEST and v2quad in Hemisphere.WEST: hem = Hemisphere.WEST cmp1, cmp2 = get_east_west_cmp() elif (v1.direction - v2.direction > pi / 2 == v1quad in Hemisphere.WEST): cmp1 = cmp2 = 1 else: cmp1 = cmp2 = -1 def is_possible_collision(x, y): in_correct_hemisphere = ( ((Vector2D((x, y)) - self.position).direction.quadrant in hem) if hem is not None else True ) return (cmp(y - p1.y, tan(v1.direction) * (x - p1.x)) == cmp1 and cmp(y - p2.y, tan(v2.direction) * (x - p2.x)) == cmp2 and in_correct_hemisphere) # restrict shot angles based on obstacles for other_ball in balls: if is_possible_collision(*other_ball.position): p1_to_ball = other_ball.position - p1 p2_to_ball = other_ball.position - p2 a1 = abs(p1_to_ball.direction - v1.direction) a2 = abs(p2_to_ball.direction - v2.direction) if min(a1, a2) > abs(v1.direction - v2.direction): raise ImpossibleShotError("Shot fully obstructed by balls.") if a1 < a2: v1.direction = p1_to_ball.direction else: v2.direction = p2_to_ball.direction # assert not is_possible_collision(*other_ball.position) # calculate necessary force to transfer to target, and sum with the # length of the shot v1_v2_avg = Vector2D(v1 + v2) / 2 force_offset_angle = abs(target.force.direction - v1_v2_avg.direction) if force_offset_angle > pi / 2: raise ImpossibleShotError("Positive force cannot be applied due to " "shot angle.") force_magnitude = (target.force.magnitude / cos(force_offset_angle) + v1_v2_avg.magnitude) # calculate target from shot vectors and necessary force target_p1 = self.position + Vector2D.from_polar(-Ball.RADIUS * 2, v1.direction) target_p2 = self.position + Vector2D.from_polar(-Ball.RADIUS * 2, v2.direction) target_force = Vector2D.from_polar(force_magnitude, v1_v2_avg.direction) self._target = ShotTarget(target_p1, target_p2, target_force) # save shot vectors self._vector1 = v1 self._vector2 = v2 # create renderer self._renderer = ShotSegmentRenderer(actor_ball.number, self.position, self.target, self.vector1, self.vector2)