Проблема, с которой вы столкнулись
Вы столкнулись с проблемой Сборщика купонов , потому что вы используете метод Отклонение выборки .
Вы также делаете сильные предположения о том, что такое "случайное заполнение". Ваш алгоритм оставит большие промежутки между кругами; это то, что вы подразумеваете под "случайным" Тем не менее, это совершенно правильное определение, и я одобряю его.
Решение
Чтобы адаптировать ваше текущее "случайное заполнение", чтобы избежать проблемы сборщика купонов при отбраковке выборки, просто разделите пространство, которое вы заполняете, в сетку. Например, если ваши круги имеют радиус 1, разделите больший круг на сетку блоков шириной 1 / квт (2). Когда становится невозможно заполнить сетку, игнорируйте эту сетку, когда вы выбираете новые точки. Проблема решена!
Возможные опасности
Однако вы должны быть осторожны при кодировании! Возможные опасности:
- Если вы делаете что-то вроде
if (random point in invalid grid){ generateAnotherPoint() }
, тогда вы игнорируете идею выгоды / ядра этой оптимизации.
- Если вы сделаете что-то вроде
pickARandomValidGridbox()
, то вы немного уменьшите вероятность создания кругов у края большего круга (хотя это может быть хорошо, если вы делаете это для графического художественного проекта, а не для научного или математический проект); однако, если вы сделаете размер сетки 1 / sqrt (2), умноженный на радиус круга, вы не столкнетесь с этой проблемой, потому что будет невозможно нарисовать блоки на краю большого круга, и, таким образом, вы можете игнорировать все поля сетки на краю.
Осуществление
Таким образом, обобщение вашего метода, чтобы избежать проблемы купонного сборщика, выглядит следующим образом:
Inputs: large circle coordinates/radius(R), small circle radius(r)
Output: set of coordinates of all the small circles
Algorithm:
divide your LargeCircle into a grid of r/sqrt(2)
ValidBoxes = {set of all gridboxes that lie entirely within LargeCircle}
SmallCircles = {empty set}
until ValidBoxes is empty:
pick a random gridbox Box from ValidBoxes
pick a random point inside Box to be center of small circle C
check neighboring gridboxes for other circles which may overlap*
if there is no overlap:
add C to SmallCircles
remove the box from ValidBoxes # possible because grid is small
else if there is an overlap:
increase the Box.failcount
if Box.failcount > MAX_PERGRIDBOX_FAIL_COUNT:
remove the box from ValidBoxes
return SmallCircles
(*) Этот шаг также является важной оптимизацией, которую я могу только предположить, что у вас ее еще нет. Без этого ваша функция didThisCircleOverlapAnother (...) невероятно неэффективна - O(N)
на запрос, что делает невозможным заполнение кругов при больших отношениях R>>r
.
Это точное обобщение вашего алгоритма, позволяющее избежать медлительности и в то же время сохранить элегантность случайности.
Обобщение для более крупных нерегулярных функций
edit: Поскольку вы отметили, что это игра, и вас интересуют неправильные формы, вы можете обобщить это следующим образом. Для любой маленькой неправильной формы заключите ее в круг, который показывает, насколько далеко вы хотите, чтобы он был от вещей. Ваша сетка может иметь размер наименьшего элемента местности. Большие функции могут включать смежные блоки 1x2, 2x2, 3x2, 3x3 и т. Д. Обратите внимание, что во многих играх с функциями, которые охватывают большие расстояния (горы) и небольшие расстояния (факелы), часто требуются сетки, которые рекурсивно разбиваются (то есть некоторые блоки разбиваются на дополнительные субблоки 2x2 или 2x2x2), создавая древовидную структуру. Эта структура с обширной бухгалтерией позволит вам случайным образом размещать смежные блоки, однако это требует много кодирования. Однако вы можете использовать алгоритм круговой сетки, чтобы сначала разместить более крупные объекты (когда на карте достаточно места для работы, и вы можете просто проверить соседние ячейки сетки на коллекцию, не сталкиваясь с проблемой сборщика купонов), тогда поместите меньшие особенности. Если вы можете разместить свои функции в этом порядке, это почти не требует дополнительного кодирования, кроме проверки соседних ячеек сетки на наличие коллизий, когда вы размещаете 1x2 / 3x3 / и т. Д. группа.