Заполнение пространства кружками неравного размера - PullRequest
12 голосов
/ 16 марта 2012

Вот моя проблема:

  • У меня есть несколько кругов, которые мне нужно отобразить внутри холста.
  • Существует произвольное количество кругов, каждый с заранее определенным радиусом.
  • Суммированная площадь кругов всегда меньше площади холста.

Я хочу расположить круги так, чтобы они занимали максимально возможное пространство внутри холста, не касаясь друг друга.Моя цель - добиться визуально приятного эффекта, когда круги хорошо распределены внутри холста.Я не знаю, действительно ли это «заполнение пространства», поскольку моя цель состоит не в том, чтобы минимизировать расстояние между элементами, а скорее в максимизации .

Вот пример того, чего я пытаюсь достичь:

Circles

Моей первой идеей "грубой силы" было следующее:

  1. Для каждого круга: рассчитать кратчайшее расстояние между его границей и границей каждого круга;Суммируйте все эти расстояния, назовите это X.
  2. Вычислите сумму всех X.
  3. Произвольно измените расстояния между кругами.
  4. Повторите 1-3 для предустановкиколичество итераций и принять максимальное значение, полученное на шаге (2).

Однако это не кажется элегантным;Я уверен, что есть лучший способ сделать это.Есть ли существующий алгоритм для достижения такого макета?Есть ли какая-либо существующая библиотека, которую я мог бы использовать (JavaScript или Ruby) для достижения этой цели?

Редактировать

Вот версия Javascript изпринятый ответ, который использует Рафаэль, чтобы нарисовать круги.

Ответы [ 3 ]

10 голосов
/ 17 марта 2012

Я бы попытался вставить сферу за сферой (первой по величине).Каждый из них добавляется в самое большое доступное пространство со случайным дрожанием.

Один из относительно простых способов найти (более или менее) самое большое доступное пространство - представить сетку точек на вашем виде и сохранить длякаждая точка сетки (в двумерном массиве) является ближайшим расстоянием к любому элементу: краю или сфере, в зависимости от того, что ближе всего.Этот массив обновляется по мере добавления каждой новой сферы.

Чтобы добавить новую сферу, просто возьмите точку сетки с наибольшим расстоянием и примените случайное дрожание (вы на самом деле знаете, сколько вы можете дрожать, потому что вы знаете расстояние до ближайшего элемента).(Я бы рандомизировал не более чем (dr) / 2, где d - это расстояние в массиве, а r - радиус добавляемой сферы.

Обновление этого массива после добавления еще одного круга - не ракетостроение: вырассчитайте для каждой точки сетки расстояние до вновь добавленной сферы и замените сохраненное значение, если оно было больше.

Возможно, ваша сетка слишком грубая, и вы не можете добавить больше окружности (когда 2DМассив не содержит расстояний, превышающих радиус круга, который нужно добавить. Затем вам нужно увеличить (например, удвоить) разрешение сетки, прежде чем продолжить.

Вот некоторые результаты этой реализации (у меня ушло около 100 строккода)

  • 100 Круги различного размера

100 circles of varying size

  • 500 Круги различного размера

500 circles of varying size

  • 100 Круги одинакового размера

enter image description here

А вот небольшой пример кода C ++ (только алгоритм, донне ожидайте, что это скомпилировать)

    // INITIALIZATION

    // Dimension of canvas
    float width = 768;
    float height = 1004;

    // The algorithm creates a grid on the canvas
    float gridSize=10;

    int gridColumns, gridRows;
    float *dist;

    void initDistances()
    {
      // Determine grid dimensions and allocate array
      gridColumns = width/gridSize;
      gridRows = height/gridSize;

      // We store a 2D array as a 1D array:
      dist = new float[ gridColumns * gridRows ];

      // Init dist array with shortest distances to the edges
      float y = gridSize/2.0;
      for (int row=0; row<gridRows; row++)
      {
        float distanceFromTop = y;
        float distanceFromBottom = height-y;
        for (int col=0; col<gridColumns; col++)
        {
          int i = row*gridColumns+col;
          dist[i]=(distanceFromTop<distanceFromBottom?distanceFromTop:distanceFromBottom);
        }
        y+=gridSize;
      }
      float x = gridSize/2.0;
      for (int col=0; col<gridColumns; col++)
      {
        float distanceFromLeft = x;
        float distanceFromRight = width-x;
        for (int row=0; row<gridRows; row++)
        {
          int i = row*gridColumns+col;
          if (dist[i]>distanceFromLeft) dist[i] = distanceFromLeft;
          if (dist[i]>distanceFromRight) dist[i] = distanceFromRight;
        }
        x+=gridSize;
      }
    }

    void drawCircles()
    {
      for (int circle = 0; circle<getNrOfCircles(); circle++)
      {
        // We assume circles are sorted large to small!
        float radius = getRadiusOfCircle( circle ); 

        // Find gridpoint with largest distance from anything
        int i=0;
        int maxR = 0;
        int maxC = 0;
        float maxDist = dist[0];

        for (int r=0; r<gridRows; r++) 
          for (int c=0; c<gridColumns; c++)
          {
            if (maxDist<dist[i]) {
              maxR= r; maxC= c; maxDist = dist[i];
            }
            i++;
          }

        // Calculate position of grid point
        float x = gridSize/2.0 + maxC*gridSize;
        float y = gridSize/2.0 + maxR*gridSize;

        // Apply some random Jitter
        float offset = (maxDist-radius)/2.0;
        x += (rand()/(float)RAND_MAX - 0.5) * 2 * offset;
        y += (rand()/(float)RAND_MAX - 0.5) * 2 * offset;


        drawCircle(x,y,radius);


        // Update Distance array with new circle;
        i=0;
        float yy = gridSize/2.0;
        for (int r=0; r<gridRows; r++)
        {
          float xx = gridSize/2.0;
          for (int c=0; c<gridColumns; c++)
          {
            float d2 = (xx-x)*(xx-x)+(yy-y)*(yy-y);

            // Naive implementation
            // float d = sqrt(d2) - radius;
            // if (dist[i]>d) dist[i] = d;

            // Optimized implementation (no unnecessary sqrt)
            float prev2 = dist[i]+radius;
            prev2 *= prev2;
            if (prev2 > d2)
            {
              float d = sqrt(d2) - radius;
              if (dist[i]>d) dist[i] = d;
            }



            xx += gridSize;
            i++;
          }
          yy += gridSize;
        }
      }
    }
2 голосов
/ 16 марта 2012

Возможно, какое-нибудь применение принудительно-направленного макета было бы полезно.

1 голос
/ 17 марта 2012

Поскольку ваша цель - просто «добиться приятного эффекта», а не решить математическую задачу, вам следует попробовать самый простой алгоритм, который может сработать первым, и посмотреть, хорошо ли он выглядит. Там не должно быть необходимости использовать очень сложную математику.

Я понимаю, что вы хотите, чтобы сферы "заполняли" доступное пространство, не оставляя больших пустых областей, в то время как другие области переполнены. Вы также хотите, чтобы макет казался случайным, а не выровненным по сетке или чем-то в этом роде.

Очевидный, очень простой способ добиться этого - просто разместить шары одну за другой в случайных местах. Если один приземляется поверх уже размещенной сферы, генерируйте другое случайное место, пока не найдете тот, который подходит.

Похоже, на изображении около 40 сфер. Вероятность того, что 40 сфер все приземлятся в одной и той же области изображения, оставляя остальную часть изображения пустой, очень и очень мала. По мере увеличения количества сфер шансы получить очень несбалансированный макет станут близки к нулю.

Попробуйте сначала и посмотрите, соответствует ли оно вашим потребностям. Если этого недостаточно, вы сможете использовать очень простую математику для смещения случайно выбранных мест в пользу выбора пустых областей. Не должно быть необходимости использовать сложные алгоритмы.

...