def find_docking_points_in_uncertainty_area(uncertainty_area_pose, length,
                                            width):
    """Find the docking points on the perimeter of the uncertainty area.

    Arguments:
        uncertainty_area_pose (float[]): uncertainty area pose
        length (float): box length in meters
        width (float): box width in meters

    Returns:
        A list containing the docking points and the normal directions, one for each robot.
    """
    max_length = max(length, width)
    min_length = min(length, width)

    uncertainty_geometry = BoxGeometry(2 * max_length,\
                                       max_length,\
                                       [uncertainty_area_pose[0],\
                                        uncertainty_area_pose[1]],\
                                       uncertainty_area_pose[2])

    edges = [uncertainty_geometry.edge(1, 2), uncertainty_geometry.edge(3, 0)]
    points = [edges[0].point((1.0/2 * min_length) / max_length),\
              edges[1].point((max_length - min_length) / max_length + (1.0/2 * min_length) / max_length)]
    normals = [edge.normal() for edge in edges]

    results = [{'point': points[i], 'normal': normals[i]} for i in range(2)]

    return results
def find_docking_points_in_uncertainty_area(uncertainty_area_pose, length, width):
    """Find the docking points on the perimeter of the uncertainty area.

    Arguments:
        uncertainty_area_pose (float[]): uncertainty area pose
        length (float): box length in meters
        width (float): box width in meters

    Returns:
        A list containing the docking points and the normal directions, one for each robot.
    """    
    max_length = max(length, width)
    min_length = min(length, width)

    uncertainty_geometry = BoxGeometry(2 * max_length,\
                                       max_length,\
                                       [uncertainty_area_pose[0],\
                                        uncertainty_area_pose[1]],\
                                       uncertainty_area_pose[2])

    edges = [uncertainty_geometry.edge(1,2), uncertainty_geometry.edge(3,0)]
    points = [edges[0].point((1.0/2 * min_length) / max_length),\
              edges[1].point((max_length - min_length) / max_length + (1.0/2 * min_length) / max_length)]
    normals = [edge.normal() for edge in edges]
            
    results = [{'point' : points[i], 'normal' : normals[i]} for i in range(2)]

    return results
def find_docking_points_to_rotate(box_current_pose, box_goal_pose, box_length, box_width):
    """Find the docking points on the perimeter of the box required
    by the robots in order to rotate the box.

    Arguments:
        box_current_pose (float[]): current box pose
        box_goal_pose (float[]): goal box pose
        length (float): box length in meters
        width (float): box width in meters

    Returns:
        A list containing the docking points and the normal directions, one for each robot,
        and the best angular position of the box required to push it.
    """
    box_pose = [box_current_pose[0], box_current_pose[1]]
    box_theta = box_current_pose[2]
                        
    # Create a box geometry object
    box_geometry = BoxGeometry(box_length, box_width, box_pose, box_theta)
                         
    p0_pg = np.array(box_goal_pose) - np.array(box_pose)
    los_angle = np.arctan2(p0_pg[1], p0_pg[0])
    references = [angle_normalization(los_angle + np.pi / 4),\
                  angle_normalization(los_angle - np.pi / 4),
                  angle_normalization(los_angle + np.pi /4 + np.pi / 2),
                  angle_normalization(los_angle - np.pi /4 - np.pi / 2)]

    differences = [angle_normalization(reference - box_theta) for reference in references]
    
    edges = [box_geometry.edge(1,2), box_geometry.edge(0, 1), box_geometry.edge(2, 3)]
    normals = [edge.normal() for edge in edges]

    for angle in [0, np.pi/2, -np.pi/2, np.pi]:
        if np.allclose(differences[0], angle):
            results = [{'point' : 0, 'normal' : normals[i]} for i in range(3)]
            return False, 0, 0, results

    abs_differences = np.array([abs(difference) for difference in differences])
    argmin_differences = np.argmin(abs_differences)

    direction = 0
    if differences[argmin_differences] < 0:
        # Clockwise rotation
        point_position = [1.0 / 2, 1.0 / 4, 1.0 / 4]
        points = [edges[i].point(point_position[i]) for i in range(3)]
        direction = - 1
    else:
        # Counterclockwise rotation
        point_position = [1.0 / 2, 3.0 / 4, 3.0 / 4]
        points = [edges[i].point(point_position[i]) for i in range(3)]
        direction = + 1
        
    results = [{'point' : points[i], 'normal' : normals[i]} for i in range(3)]

    return True, references[argmin_differences], direction, results
def find_docking_points_to_push(box_current_pose, box_goal_pose, box_length,
                                box_width):
    """Find the docking points on the perimeter of the box required
    by the robots in order to move the box to the goal position.

    Arguments:
        box_current_pose (float[]): current box pose
        box_goal_pose (float[]): goal box pose
        length (float): box length in meters
        width (float): box width in meters

    Returns:
        A list containing the docking points and the normal directions, one for each robot.
    """
    singularity = False
    box_pose = [box_current_pose[0], box_current_pose[1]]
    box_theta = box_current_pose[2]

    # Create a box geometry object
    box_geometry = BoxGeometry(box_length, box_width, box_pose, box_theta)

    # Test if (box_goal_pose - box_pose) is parallel to an edge of the box
    # difference = np.array(box_goal_pose) - np.array(box_pose)
    # angle_between = angle_normalization(np.arctan2(difference[1], difference[0]) - box_theta)
    # tolerance = 0.1
    # for angle in [0, + np.pi / 2, - np.pi / 2, np.pi]:
    #     if abs(angle_between - angle) < tolerance:
    #         singularity = True
    #         break

    # Calculate the distances between the goal and the vertices
    distances = [
        np.linalg.norm(np.array(box_goal_pose) - np.array(v))
        for v in box_geometry.vertices()
    ]
    # Take into account the indexes of the vertices
    dist_indexed = [(i, distances[i]) for i in range(4)]
    # Sort the distances
    dist_ordered = sorted(dist_indexed, key=lambda item: item[1], reverse=True)

    # Indexes of interest
    argmax0 = dist_ordered[0][0]
    argmax1 = dist_ordered[1][0]
    argmax2 = dist_ordered[2][0]
    pair_01 = [argmax0, argmax1]
    pair_01.sort()
    pair_02 = [argmax0, argmax2]
    pair_02.sort()
    # In case of pair_0i[0] == 0 and pair__0i[1] == 3 they should be reversed
    if pair_01[0] == 0 and pair_01[1] == 3:
        pair_01.reverse()
    if pair_02[0] == 0 and pair_02[1] == 3:
        pair_02.reverse()

    points = []
    edges = []
    # Find docking points and normals
    if singularity == True:
        edges.append(box_geometry.edge(*pair_01))
        edges.append(
            box_geometry.edge((pair_01[0] - 1) % 4, (pair_01[1] - 1) % 4))
        edges.append(
            box_geometry.edge((pair_01[0] + 1) % 4, (pair_01[1] + 1) % 4))
        points = [1.0 / 2] * 3
    else:
        shorter_edge = box_geometry.edge(*pair_01)
        longer_edge = box_geometry.edge(*pair_02)
        if shorter_edge >= longer_edge:
            shorter_edge, longer_edge = longer_edge, shorter_edge
        edges.append(shorter_edge)
        edges.append(longer_edge)
        edges.append(longer_edge)
        points = [1.0 / 2, 1.0 / 4, 3.0 / 4]

    return [{
        'point': edges[i].point(points[i]),
        'normal': edges[i].normal()
    } for i in range(3)]
def find_docking_points_to_rotate(box_current_pose, box_goal_pose, box_length,
                                  box_width):
    """Find the docking points on the perimeter of the box required
    by the robots in order to rotate the box.

    Arguments:
        box_current_pose (float[]): current box pose
        box_goal_pose (float[]): goal box pose
        length (float): box length in meters
        width (float): box width in meters

    Returns:
        A list containing the docking points and the normal directions, one for each robot,
        and the best angular position of the box required to push it.
    """
    box_pose = [box_current_pose[0], box_current_pose[1]]
    box_theta = box_current_pose[2]

    # Create a box geometry object
    box_geometry = BoxGeometry(box_length, box_width, box_pose, box_theta)

    p0_pg = np.array(box_goal_pose) - np.array(box_pose)
    los_angle = np.arctan2(p0_pg[1], p0_pg[0])
    references = [angle_normalization(los_angle + np.pi / 4),\
                  angle_normalization(los_angle - np.pi / 4),
                  angle_normalization(los_angle + np.pi /4 + np.pi / 2),
                  angle_normalization(los_angle - np.pi /4 - np.pi / 2)]

    differences = [
        angle_normalization(reference - box_theta) for reference in references
    ]

    edges = [
        box_geometry.edge(1, 2),
        box_geometry.edge(0, 1),
        box_geometry.edge(2, 3)
    ]
    normals = [edge.normal() for edge in edges]

    for angle in [0, np.pi / 2, -np.pi / 2, np.pi]:
        if np.allclose(differences[0], angle):
            results = [{'point': 0, 'normal': normals[i]} for i in range(3)]
            return False, 0, 0, results

    abs_differences = np.array([abs(difference) for difference in differences])
    argmin_differences = np.argmin(abs_differences)

    direction = 0
    if differences[argmin_differences] < 0:
        # Clockwise rotation
        point_position = [1.0 / 2, 1.0 / 4, 1.0 / 4]
        points = [edges[i].point(point_position[i]) for i in range(3)]
        direction = -1
    else:
        # Counterclockwise rotation
        point_position = [1.0 / 2, 3.0 / 4, 3.0 / 4]
        points = [edges[i].point(point_position[i]) for i in range(3)]
        direction = +1

    results = [{'point': points[i], 'normal': normals[i]} for i in range(3)]

    return True, references[argmin_differences], direction, results
def find_docking_points_to_push(box_current_pose, box_goal_pose, box_length, box_width):
    """Find the docking points on the perimeter of the box required
    by the robots in order to move the box to the goal position.

    Arguments:
        box_current_pose (float[]): current box pose
        box_goal_pose (float[]): goal box pose
        length (float): box length in meters
        width (float): box width in meters

    Returns:
        A list containing the docking points and the normal directions, one for each robot.
    """
    singularity = False
    box_pose = [box_current_pose[0], box_current_pose[1]]
    box_theta = box_current_pose[2]
                        
    # Create a box geometry object
    box_geometry = BoxGeometry(box_length, box_width, box_pose, box_theta)
                         
    # Test if (box_goal_pose - box_pose) is parallel to an edge of the box
    # difference = np.array(box_goal_pose) - np.array(box_pose)
    # angle_between = angle_normalization(np.arctan2(difference[1], difference[0]) - box_theta)
    # tolerance = 0.1
    # for angle in [0, + np.pi / 2, - np.pi / 2, np.pi]:
    #     if abs(angle_between - angle) < tolerance:
    #         singularity = True
    #         break

    # Calculate the distances between the goal and the vertices
    distances = [np.linalg.norm(np.array(box_goal_pose) - np.array(v)) for v in box_geometry.vertices()]
    # Take into account the indexes of the vertices
    dist_indexed = [(i, distances[i]) for i in range(4)]
    # Sort the distances
    dist_ordered =  sorted(dist_indexed, key = lambda item : item[1], reverse = True)

    # Indexes of interest
    argmax0 = dist_ordered[0][0]
    argmax1 = dist_ordered[1][0]
    argmax2 = dist_ordered[2][0]
    pair_01 = [argmax0, argmax1]
    pair_01.sort()
    pair_02 = [argmax0, argmax2]
    pair_02.sort()
    # In case of pair_0i[0] == 0 and pair__0i[1] == 3 they should be reversed
    if pair_01[0] == 0 and pair_01[1] == 3:
        pair_01.reverse()
    if pair_02[0] == 0 and pair_02[1] == 3:
        pair_02.reverse()

    points = []
    edges = []
    # Find docking points and normals
    if singularity == True:
        edges.append(box_geometry.edge(*pair_01))
        edges.append(box_geometry.edge((pair_01[0] - 1) % 4, (pair_01[1] - 1) % 4))
        edges.append(box_geometry.edge((pair_01[0] + 1) % 4, (pair_01[1] + 1) % 4))
        points = [1.0 / 2] * 3
    else:
        shorter_edge = box_geometry.edge(*pair_01)
        longer_edge = box_geometry.edge(*pair_02)
        if shorter_edge >= longer_edge:
            shorter_edge, longer_edge = longer_edge, shorter_edge
        edges.append(shorter_edge)
        edges.append(longer_edge)
        edges.append(longer_edge)
        points = [1.0 / 2, 1.0 / 4, 3.0 / 4]
        
    return [{'point' : edges[i].point(points[i]) , 'normal' : edges[i].normal() } for i in range(3)]