Упаковка неправильных кругов на поверхности сферы

Я использую THREE.js для создания точек на сфере, аналогично пример периодической таблицы элементов

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

Вот примеры этой идеи в действии:

vimeo

pic

круглый java-апплет

Может ли кто-нибудь помочь мне указать направление поиска алгоритма, который позволит мне это сделать? Соотношение упаковки не должно быть супер высоким, и в идеале это было бы что-то быстрое и легкое для вычисления в javascript для рендеринга в THREE.js(декартовой или координатной системе). Здесь важна эффективность.

Изменить: радиус окружности может широко варьироваться. Вот пример использования кода периодической таблицы: example

Ответ 1

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

//random point on sphere of radius R
var sphereCenters = []
var numSpheres = 100;
for(var i = 0; i < numSpheres; i++) {
    var R = 1.0;
    var vec = new THREE.Vector3(Math.random(), Math.random(), Math.random()).normalize();
    var sphereCenter = new THREE.Vector3().copy(vec).multiplyScalar(R);
    sphereCenter.radius = Math.random() * 5; // RANDOM SPHERE SIZE, plug in your sizes here
    sphereCenters.push(sphereCenter);
    //Create a THREE.js sphere at sphereCenter
    ...

}

Затем несколько раз выполните приведенный ниже код, чтобы эффективно упаковать сферы:

for(var i = 0; i < sphereCenters.length; i++) {
    for(var j = 0; j < sphereCenters.length; j++) {
        if(i === j) continue;
        //calculate the distance between sphereCenters[i] and sphereCenters[j]
        var dist = new THREE.Vector3().copy(sphereCenters[i]).sub(sphereCenters[j]);
        if(dist.length() < sphereSize) {
             //move the center of this sphere to to compensate
             //how far do we have to move?
             var mDist = sphereSize - dist.length();
             //perturb the sphere in direction of dist magnitude mDist
             var mVec = new THREE.Vector3().copy(dist).normalize();
             mVec.multiplyScalar(mDist);
             //offset the actual sphere
             sphereCenters[i].add(mVec).normalize().multiplyScalar(R);

        }
    }
}

Запуск второй секции несколько раз "сходится" к решению, которое вы ищете. Вы должны выбрать, сколько раз он должен быть запущен, чтобы найти лучший компромисс между скоростью и точностью.

Обновлено для точности

Ответ 2

Здесь можно попробовать: итеративный поиск с использованием симулированной силы отталкивания.

Алгоритм

Сначала инициализируйте набор данных, расположив круги по поверхности в любом виде алгоритма. Это просто для инициализации, поэтому она не должна быть большой. Периодический код таблицы будет хорошо. Кроме того, присвойте каждому кругу "массу", используя его радиус в качестве значения массы.

Теперь запустите итерацию, чтобы сходиться на решении. Для каждого прохождения через основной цикл выполните следующие действия:

  • Вычислить силы отталкивания для каждого круга. Моделируйте свою силу отталкивания после формулы для силы тяжести с двумя корректировками: (a) объекты должны отталкиваться друг от друга, не привлекаться друг к другу, и (b) вам нужно настроить значение "сила постоянной" на соответствуют масштабу вашей модели. В зависимости от вашей математической способности вы сможете рассчитывать хорошую постоянную стоимость во время планирования; другие мудрые просто экспериментируют немного сначала, и вы найдете хорошее значение.

  • После вычисления общих сил на каждом круге (пожалуйста, посмотрите на проблему n-body, если вы не знаете, как это сделать), перемещайте каждый круг вдоль вектора его общей расчетной силы, используя длину вектора как расстояния до перемещения. Здесь вы можете обнаружить, что вам нужно настроить значение постоянной силы. Сначала вам понадобятся движения с длиной, которая составляет менее 5% от радиуса сферы.

  • Движения на шаге 2 будут вытолкнуть круги с поверхности сферы (потому что они отталкивают друг друга). Теперь переместите каждый круг обратно на поверхность сферы в направлении к центру сферы.

  • Для каждого круга вычислите расстояние от старой позиции круга до ее нового положения. Наибольшее перемещенное расстояние - это длина движения для этой итерации в основном цикле.

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

Tweaking

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

f = ( k * m[i] * m[j] ) / ( r * r );

Вы можете попробовать следующее:

f = ( k * m[i] * m[j] ) / pow( r, p );

Затем вы можете поэкспериментировать с разными значениями p.

Вы также можете поэкспериментировать с различными алгоритмами для исходного распределения.

Количество проб и ошибок будет зависеть от ваших целей разработки.

Ответ 3

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

Вот код, который у них есть:

            var vector = new THREE.Vector3();

            for ( var i = 0, l = objects.length; i < l; i ++ ) {

                var phi = Math.acos( -1 + ( 2 * i ) / l );
                var theta = Math.sqrt( l * Math.PI ) * phi;

                var object = new THREE.Object3D();

                object.position.x = 800 * Math.cos( theta ) * Math.sin( phi );
                object.position.y = 800 * Math.sin( theta ) * Math.sin( phi );
                object.position.z = 800 * Math.cos( phi );

                vector.copy( object.position ).multiplyScalar( 2 );

                object.lookAt( vector );

                targets.sphere.push( object );

            }