Реализовать алгоритм Boids

18

Вступление

Boids алгоритм является относительно простой демонстрацией поведения эмерджентного в группе. У него есть три основных правила, описанных его создателем Крейгом Рейнольдсом:

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

  • Разделение : бычок , чтобы избежать скученности местных flockmates.
  • Выравнивание : держитесь в сторону среднего курса местных флокматов.
  • Сплоченность : держитесь, чтобы двигаться к среднему положению местных флокматов.

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

Я не идеален при объяснении вещей, поэтому я настоятельно рекомендую проверить источник . У него также есть несколько супер информативных фотографий на своем сайте.

Вызов

Учитывая количество boids (моделируемых объектов) и количество кадров, выведите анимацию моделирования.

  • Боиды должны отображаться в виде красного круга с линией внутри круга, показывающей его направление, то есть направление, на которое указывает боид:

Грубый рисунок двух "boids", один направлен влево, а другой направо.

  • Угол каждого рычага (как описано Рейнольдсом) должен составлять 300 градусов. (не 360)
  • Начальный заголовок и позиция каждого штанги должны быть равномерно случайными (но затравленными, чтобы выходные данные по-прежнему определялись), а также позиция.
  • Если радиус боида равен 1, то радиус окрестности должен быть равен 3.
  • Количество боидов будет где-то 2-20.
  • Количество кадров будет где-то 1-5000
  • Анимация должна воспроизводиться с минимальным 10 миллисекундами на кадр и максимальным количеством секунд, которое увеличивается в 1 секунду. (2 boids = 2 секунды на кадр максимум, 3 boids = 3 секунды на кадр максимум и так далее)
  • Выходная анимация должна быть не менее 5 радиусов boid на 5 радиусов boid, умноженной на половину числа boid-радиусов. Таким образом, минимальный размер для 2 boids был бы 10 boid-радиусами на 10 boid-радиусов, минимальный размер для 3 boids был бы 15 boid-радиусами на 15 boid-радиусов, и так далее.
  • Радиус каждой области должен быть не менее 5 пикселей и не более 50 пикселей.
  • Скорость каждого бида должна быть ограничена, чтобы он не перемещался более чем на 1/5 своего радиуса в одном кадре.
  • Выходные данные должны быть детерминированными, чтобы один и тот же вход давал один и тот же результат, если он запускался несколько раз.
  • Если boid достигает границы, он должен вернуться на другую сторону. Точно так же окрестности вокруг каждого бида должны также обернуться вокруг границ.

Правила для алгоритма

В этом случае каждое поле имеет сектор вокруг него, охватывающий 300 градусов, с центром в направлении заголовка. Любые другие boids в этом «соседстве» считаются «соседями», или (если использовать термин Рейнольдса) «flockmates».

  1. Каждый бид должен отрегулировать свой курс, чтобы избежать столкновений и поддерживать удобное расстояние одного радиуса бида со своими соседями. (Это аспект «разделения» алгоритма. Один радиус радиуса действия может быть обойден, но он должен быть как резиновая лента, возвращающаяся на место.)

  2. Каждый бид должен дополнительно отрегулировать свой курс так, чтобы он был ближе к среднему курсу других бидов в его окрестности, если он не мешает первому правилу. (Это аспект «выравнивания» алгоритма)

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

В своей статье на эту тему он объясняет это следующим образом:

Чтобы построить имитированное стадо, мы начнем с модели boid, которая поддерживает геометрический полет. Мы добавляем поведение, которое соответствует противоположным силам предотвращения столкновений и побуждению присоединиться к стаду. Кратко изложенные как правила, и в порядке уменьшения приоритета, поведения, которые приводят к моделируемому скоплению:

  • Предотвращение столкновений: избегайте столкновений с ближайшими соседями
  • Velocity Matching: попытка сопоставить скорость с ближайшими соседями
  • Сосредоточение стада: попытка остаться рядом с соседними стаями

Более подробное описание движения:

  • Стандартная реализация алгоритма Boids обычно выполняет вычисления для каждого из правил и объединяет их вместе.
  • По первому правилу boid проходит список соседних boids в его окрестности, и, если расстояние между ним и соседом меньше определенного значения, вектор, отталкивающий boid от is соседа, применяется к заголовку boid.
  • Для второго правила boid вычисляет средний курс своих соседей и добавляет небольшую часть (мы будем использовать 1/10 в этой задаче) разницы между его текущим и средним курсом к его текущему курсу.
  • По третьему и последнему правилу boid усредняет позиции своих соседей, вычисляет вектор, который указывает на это местоположение. Этот вектор умножается на еще меньшее число, чем то, которое использовалось для правила 2 (для этой задачи будет использоваться 1/50) и применяется к заголовку.
  • Затем груз перемещается в направлении своего направления.

Вот полезная реализация псевдокода алгоритма Boids.

Пример ввода и вывода

Входные данные:

5, 190 (5 боидов, 190 кадров)

Выход:

190-кадровая анимация алгоритма Боида с 5 бойдами.

Критерий победы

Это , поэтому выигрывает самое маленькое решение в байтах.

iPhoenix
источник
7
«В алгоритме есть, конечно, больше, поэтому я настоятельно рекомендую проверить источник». - здесь все нужно или нет? Если нет, я бы порекомендовал исправить это.
Джонатан Аллан
1
Пожалуйста, используйте песочницу, прежде чем публиковать задания, как указано на странице вопросов .
flawr
@JonathanAllan Да, все необходимое здесь, но более подробные объяснения, которые могут иметь больше смысла для других пользователей, доступны в источнике.
iPhoenix
11
Это интересная задача (я считаю, что поведение стекается увлекательно), но она должна быть четко определена, особенно для игры в гольф, иначе давление, направленное на уменьшение длины кода, приведет к всевозможным отклонениям от духа задачи быть мотивированным.
Трихоплакс

Ответы:

7

Обработка 3.3.6 (Java) ,+932 +931 940 928 +957 917 904 байта

-1 байт от Джонатана Фрека
+11 байт, чтобы лучше соответствовать спецификации
-2 байта от Кевина Круйссена
-12 байт для изменения аргументов на t ()
+29 байт, потому что я делал неправильное двоение, см. Закомментированную версию ниже
-40 байт для использования для циклы вместо отдельных вызовов для каждого ghost
-13 байтов для использования по умолчанию frameRate, 30

Ну, это начало для тех, кто не занимается Java-гольфом. :)

int n=15,f=400,i,j,z=255,w=500;float d=200./n;PVector m;B[]a=new B[n];void setup(){size(500,500);fill(z,0,0);randomSeed(n);for(i=0;i<n;a[i++]=new B(new PVector(random(w),random(w)),m.fromAngle(random(TAU))));}void draw(){background(z);for(B b:a)b.u();if(frameCount%f<1)setup();}class B{PVector p,v,e,q,r;ArrayList<B>n;B(PVector m,PVector o){p=m;v=o;}void u(){e=v.copy();n=new ArrayList();for(B b:a){if(b!=this)for(i=-w;i<=w;i+=w)for(j=-w;j<=w;j+=w)t(i,j,b);}if(n.size()>0){q=new PVector();r=q.copy();for(B b:n){q.add(b.v);r.add(b.p);if(p.dist(b.p)<=d)e.add(p).sub(b.p);}e.add(q.div(n.size()).sub(v).div(10));e.add(r.div(n.size()).sub(p).div(50));}p.add(e.limit(d/10));v=e.mult(10);p.set((p.x+w)%w,(p.y+w)%w);noStroke();ellipse(p.x,p.y,d,d);stroke(0,0,z);line(p.x,p.y,p.x+v.x,p.y+v.y);}void t(int x,int y,B o){m=o.p.copy().add(x,y);if(2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6)n.add(new B(m,o.v));}}

Я не знаю какого-либо разумного способа сделать ввод в Processing, поэтому первые две переменные - это входные данные (и я не посчитал их значения (5 байт) в счетчик байтов). Если это проблема, я могу попробовать другие вещи.

Я также не знаю хорошего способа разрешить попробовать его в Интернете (проект Processing.js не может справиться с этим стилем кода) без размещения вещей самостоятельно; и это то, что я не хочу пытаться. Дайте мне знать, если есть что-нибудь умное, что я могу сделать.

Отформатированный код с комментариями

int n=15, // Number of boids
    f=400, // Number of frames
    i,j,z=255,w=500; // temp*2, and two constants
float d=200./n; // Boid diameter
PVector m; // temp
B[]a=new B[n];
void setup(){ // This is automatically called at startup
  size(500,500); // Can't use variables for this without extra bytes for settings()
  fill(z,0,0);
  randomSeed(n); // seeded from number of Boids, so that n=19 is very different from n=20
  for(i=0;i<n;a[i++]=new B(new PVector(random(w),random(w)),m.fromAngle(random(TAU))));
}
void draw(){ // This is automatically called each frame
  background(z);
  for(B b:a)
    b.u();
  if(frameCount%f<1) // When desired frames length is hit, reset everything.
    setup();         // Could also use noLoop() instead of setup() to just stop instead.
                     // Or, remove this if statement altogether to go on to infinity.
}
class B{ // Boid
  PVector p,v,e,q,r; // Position, Velocity, Next velocity, and two temp vectors
  ArrayList<B>n; // List of neighbors
  B(PVector m,PVector o){
    p=m;
    v=o;
  }
  void u(){ // Update function, does rules and redraw for this Boid
    e=v.copy();
    n=new ArrayList();
    for(B b:a){ // Test a Boid and its eight ghosts for neighborship
      if(b!=this) // Note: Assumes neighborhood diameter < min(width,height)
        // The ghosts are to check if it'd be closer to measure by wrapping
        // We need eight for wrapping north, east, south, west, northeast,
        // northwest, southeast, and southwest. And also the non-wrapped one.
        // The above assumption ensures that each ghost is further apart than
        // the neighborhood diameter, meaning that only one neighbor might be
        // found for each boid. To test this, place a boid in each corner, right
        // to the edge, facing away from center. Each boid should find three
        // neighbors, that are the three other boids.
        for(i=-w;i<=w;i+=w)for(j=-w;j<=w;j+=w)t(i,j,b);
    }
    if(n.size()>0){
      q=new PVector();
      r=q.copy();
      for(B b:n){
        q.add(b.v); // Velocity matching, pt 1
        r.add(b.p); // Flock centering, pt 1
        if(p.dist(b.p)<=d)  
          e.add(p).sub(b.p); // Collision avoidance
      }
      e.add(q.div(n.size()).sub(v).div(10)); // Velocity matching, pt 2
      e.add(r.div(n.size()).sub(p).div(50)); // Flock centering, pt 2
    }
    p.add(e.limit(d/10)); // Update vectors
    v=e.mult(10);
    p.set((p.x+w)%w,(p.y+w)%w); // Wrapping
    noStroke();
    ellipse(p.x,p.y,d,d); // Draw Boid, finally
    stroke(0,0,z);
    line(p.x,p.y,p.x+v.x,p.y+v.y);
  }
  void t(int x,int y,B o){ // Test if a Boid (or a ghost) is a neighbor
    m=o.p.copy().add(x,y);
    if(2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6)
      n.add(new B(m,o.v));
  }
}

Образец вывода

n = 15, кадры = 400:

Boids

Или та же анимация, но показывающая соседство каждого бида.

Phlarx
источник
1
Не можете 2*PIстать, TAUчтобы сохранить байт?
Джонатан Фрех
@JonathanFrech Да, это возможно; У меня изначально был -PI, PI, и я шел таким путем, но отвлекся.
Phlarx
Моя программа (написанная на js и html) не экспортировала gif, но она нарисовала изображение, и я использовал программу захвата экрана и преобразовал экспортированное видео в gif. Однако я заметил одну вещь. Боиды имеют синий контур, который не соответствует спецификации :)
iPhoenix
Еще одно приятное напоминание, этот ответ не соответствует спецификации, поэтому он не получит награду.
iPhoenix
1
Я не знаю , Обработка, но я думаю , что вы можете играть в гольф на следующие вещи: ,i,в , ,i=0,а затем удалить i=0внутри для петли. (-1 байт); frameCount%f==0до frameCount%f<1(1 байт); &&чтобы &в финале , если 2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6(-1 байт). Опять же, не уверен, что это возможно, но поскольку обработка кажется довольно похожей на Java, я думаю, что это так. Кроме того, вы можете попробовать создать GIF с screentogif.com .
Кевин Круйссен
4

JavaScript (ES6) + HTML5, 1200 байт

Вот мое текущее решение с использованием Canvas API. eval()Возвращает каррированной функцию, первый вход является Boidпопуляция, а во- вторых это количество кадров анимации. Вы можете использовать Infinityдля непрерывной анимации.

Это eval(...)1187 байт и <canvas id=c>13 байт, что в сумме составляет 1200. CSS не является необходимым, но для удобства он позволяет видеть края холста.

eval("L7F7{function B8{t=this,t.a=o8*T,t.x=o8*S,t.y=o8*S}C=this.c,D=C.getContext`2d`,({abs:z,random:o,atan2:k,cos:u,sin:g,PI:P,T=2*P,G={c:_7A[r='filter'](b7b!=t)[i](9)79)),n:_7A[r](b7b!=t)[i](9)7({a,x,y:y-S})),s:_7A[r](b7b!=t)[i](9)7({a,x,y:y+S})),e:_7A[r](b7b!=t)[i](9)7({a,x:x-S,y})),w:_7A[r](b7b!=t)[i](9)7({a,x:x+S,y}))},M=I7[I,I+T,I-T][p]((a,x)7z(x)<z(a)?x:a)}=Math),B.prototype={d8{with(D)save8,translate(x,y),rotate(a),beginPath8,arc(0,0,5,0,T),fillStyle='red',fill8,beginPath8,moveTo(0,0),lineTo(10,0),strokeStyle='blue',stroke8,restore8},n:_7(({c,n,s,e,w}=G),c8.concat(n8,s8,e8,w8)[r](b7(d=b.x-x,f=b.y-y,400>d*d+f*f&&z(z(k(f,d)-a)/P-1)>1/6))),s8{q=(j=t.n8).length,v=t.v8||0,l=t.l8||0,f=t.f8||0,a=t.a=(t.a+v+l/10+f/50)%T,t.x=(x+u(a)+S)%S,t.y=(y+g(a)+S)%S},v:_7([d,f]=j[r](b7225>(b.x-x)**2+(b.y-y)**2)[p='reduce'](([d,f],b)7[x+d-b.x,y+f-b.y],[0,0]),d||f?M(k(f,d)-a):0),l:_7j[i](b7M(b.a-a))[p]((a,x)7a+x,0)/q,f:_7([d,f]=j[p](([d,f],b)7[d+b.x,f+b.y],[-x*q,-y*q]),d||f?M(k(f,d)-a):0)},S=C.width=C.height=50*L,A=Array(L).fill().map(_7new B),R=_7{D.clearRect(0,0,S,S),A[i='map'](b79=b).d8),A[i](b79=t=b).s8),F--&&setTimeout(R,10)},R8}".replace(/[789]/g,m=>['=>','()','({a,x,y}'][m-7]))
(10)(Infinity)
canvas{border:1px solid}
<canvas id=c>

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

В соответствии с просьбой, еще один фрагмент с входными данными для населения Boid:

b.onchange=()=>{eval("L7F7{function B8{t=this,t.a=o8*T,t.x=o8*S,t.y=o8*S}C=this.c,D=C.getContext`2d`,({abs:z,random:o,atan2:k,cos:u,sin:g,PI:P,T=2*P,G={c:_7A[r='filter'](b7b!=t)[i](9)79)),n:_7A[r](b7b!=t)[i](9)7({a,x,y:y-S})),s:_7A[r](b7b!=t)[i](9)7({a,x,y:y+S})),e:_7A[r](b7b!=t)[i](9)7({a,x:x-S,y})),w:_7A[r](b7b!=t)[i](9)7({a,x:x+S,y}))},M=I7[I,I+T,I-T][p]((a,x)7z(x)<z(a)?x:a)}=Math),B.prototype={d8{with(D)save8,translate(x,y),rotate(a),beginPath8,arc(0,0,5,0,T),fillStyle='red',fill8,beginPath8,moveTo(0,0),lineTo(10,0),strokeStyle='blue',stroke8,restore8},n:_7(({c,n,s,e,w}=G),c8.concat(n8,s8,e8,w8)[r](b7(d=b.x-x,f=b.y-y,400>d*d+f*f&&z(z(k(f,d)-a)/P-1)>1/6))),s8{q=(j=t.n8).length,v=t.v8||0,l=t.l8||0,f=t.f8||0,a=t.a=(t.a+v/3+l/10+f/50)%T,t.x=(x+u(a)+S)%S,t.y=(y+g(a)+S)%S},v:_7([d,f]=j[r](b7225>(b.x-x)**2+(b.y-y)**2)[p='reduce'](([d,f],b)7[x+d-b.x,y+f-b.y],[0,0]),d||f?M(k(f,d)-a):0),l:_7j[i](b7M(b.a-a))[p]((a,x)7a+x,0)/q,f:_7([d,f]=j[p](([d,f],b)7[d+b.x,f+b.y],[-x*q,-y*q]),d||f?M(k(f,d)-a):0)},S=C.width=C.height=50*L,A=Array(L).fill().map(_7new B),R=_7{D.clearRect(0,0,S,S),A[i='map'](b79=b).d8),A[i](b79=t=b).s8),F--&&setTimeout(R,10)},R8}".replace(/[789]/g,m=>['=>','()','({a,x,y}'][m-7]))(+b.value)(Infinity);b.remove()}
input{display:block}canvas{border:1px solid}
<input id=b><canvas id=c>

Патрик Робертс
источник
Кажется, что boids не взаимодействуют, когда я запускаю сниппет
Джо Кинг,
@ JoKing это должно быть исправлено сейчас
Патрик Робертс
Проблема заключалась в том, что минимизатор babel скрывал глобальную переменную в одной функции с именем параметра, а неявное приведение типа к числу не выдавало ошибку, поэтому функция просто молча провалилась и никогда не обнаруживала соседей.
Патрик Робертс
Завтра вечером я постараюсь сделать интерактивную демонстрацию, но сегодня вечером у меня кончились силы.
Патрик Робертс
Просто примечание: где он читает t.a+v+l/10+f/50, если вы измените это на t.a+v/3+l/10+f/50, то это приведет к несколько более интересному поведению, но текущая программа меньше и все еще требует спецификаций.
Патрик Робертс