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)