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
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)