def poisson_disk_placement_on_sphere_given_seed_points(accepted_points, seed_points, min_point_dist=0.05, min_angle_degrees=5, max_angle_degrees=10, num_child_points=20, failure_limit=5, num_adjacent=1):
    """
    Generates a list of cartesian points on the surface of a sphere using a poisson disks approach
    Uses 3D spatial binning to prevent requiring exhaustive search through all points.

    :param seed_points: List of cartesian starting points from which child points will be grown
    :param min_point_dist: Minimum angle allowed between two points (and therefore is the minimum cell diameter also)
    :param min_angle: Minimum angle (in degrees) by which child points must be offset from their parent/seed point
    :param max_angle: Maximum angle (in degrees) by which child points must be offset from their parent/seed point
    :param num_child_points: Number of child points to be attempted
    :param failure_limit: Maximum number of children which can fail to be placed before child generation
     is abandoned for current seed point
    :param num_adjacent = Number of adjacent spatial bins in each direction that will be searched through to find
    points that may violate minimum distance rule
    :return: List of cartesian points on surface of sphere which are minimum of min_point_dist from each other
    """
    min_angle_radians = radians(min_angle_degrees)
    max_angle_radians = radians(max_angle_degrees)
    spatially_binned_points = dict()
    # Each bin is the width of min_dist
    clamp_factor = float(90) / float(min_point_dist)
    # Put all already-accepted points into spatial structure so they can be searched against
    for existing_point in accepted_points:
        jellymatter_voronoi.list_dict_add(spatially_binned_points, clamp_point_to_bucket(existing_point, clamp_factor), existing_point)

    # Begin generating new points from seeds: for each seed point, generate child points within min/max
    for seed_point in seed_points:
        # Test if seed_point is at a 'safe' location
        seed_point_bin_key = clamp_point_to_bucket(seed_point, clamp_factor)
        if adjacent_bins_are_safe(spatially_binned_points, seed_point, seed_point_bin_key, num_adjacent, min_point_dist, clamp_factor):
            # Continue using seed_point for children even if it itself is not accepted
            accepted_points.append(seed_point)
        # Store point in spatial bin's list (or create list and store if it doesn't exist)
        jellymatter_voronoi.list_dict_add(spatially_binned_points, clamp_point_to_bucket(seed_point, clamp_factor), seed_point)
        # Generate new points
        bad_children = 0
        good_children = 0
        sequential_bad_children = 0
        while good_children < num_child_points:
            # If a certain number of children fail to be placed in a row, abandon
            # child placement for this seed. This avoids spending too long for a
            # generally non-viable position
            if sequential_bad_children >= failure_limit:
                break
            new_p = rotate_to_vec_in_anulus_in_radians(seed_point, min_angle_radians, max_angle_radians)
            new_p_bin_key = clamp_point_to_bucket(new_p, clamp_factor)
            # Searched all of clamps
            if adjacent_bins_are_safe(spatially_binned_points, new_p, new_p_bin_key, num_adjacent, min_point_dist, clamp_factor):
                # Store this point
                jellymatter_voronoi.list_dict_add(spatially_binned_points, new_p_bin_key, new_p)
                accepted_points.append(new_p)
                sequential_bad_children = 0
                good_children += 1
            else:
                bad_children += 1
                sequential_bad_children += 1
        #print("Total children: bad: \t{0}, good: \t{1}, broke after: \t{2}".format(bad_children, good_children, bad_children+good_children))
    return accepted_points
Example #2
0
    def test_arc_dist_same_over_rotation(self):
        starting_p = vector_utils.normalized([1, 1, 1])[0]
        min_angle = math.radians(15)
        max_angle = math.radians(16)
        spin_angle = math.radians(50)
        # Generate twice.. should be deterministic
        near_p = cartesian_utils.rotate_to_vec_in_anulus_in_radians(starting_p, min_angle, min_angle, spin_angle)
        far_p = cartesian_utils.rotate_to_vec_in_anulus_in_radians(starting_p, max_angle, max_angle, spin_angle)
        first_anulus_arc_width = cartesian_utils.cartesian_arc_distance(near_p, far_p)

        # Still same angle between them
        min_angle = math.radians(5)
        max_angle = math.radians(6)
        near_p = cartesian_utils.rotate_to_vec_in_anulus_in_radians(starting_p, min_angle, min_angle, spin_angle)
        far_p = cartesian_utils.rotate_to_vec_in_anulus_in_radians(starting_p, max_angle, max_angle, spin_angle)
        second_anulus_arc_width = cartesian_utils.cartesian_arc_distance(near_p, far_p)
        # Allow some tolerance
        self.assertAlmostEqual(first_anulus_arc_width, second_anulus_arc_width)