Реалізуйте алгоритм Boids


18

Вступ

Boids алгоритм є відносно простий демонстрацією поведінки Емерджентні в групі. Він має три основні правила, як описав його творець Крейг Рейнольдс:

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

  • Розділення : керуйте, щоб уникнути скупчення місцевих фетів.
  • Вирівнювання : орієнтуйтеся на середній загін місцевих флокметів.
  • Згуртованість : спрямовуйте рухатися до середньої позиції місцевих флотметів.

Кожен боїд має прямий доступ до геометричного опису всієї сцени, але стекання вимагає, щоб він реагував лише на флокметів у певному невеликому сусідстві навколо себе. Для сусідства характерні відстань (вимірюється від центру бої) та кут , виміряний від напрямку польоту боїда. Сльотовики поза цим місцевим районом ігноруються. Район можна вважати моделлю обмеженого сприйняття (як риба в каламутній воді), але, мабуть, правильніше думати про це як про визначення регіону, в якому зграї впливають на рульове керування.

Я не досконалий при поясненні речей, тому настійно рекомендую перевірити джерело . На його сайті також є кілька суперінформативних фотографій.

Виклик

Враховуючи кількість боїдів (змодельованих об'єктів) та кількість кадрів, виведіть анімацію моделювання.

  • Укладки повинні бути зображені у вигляді червоного кола, а лінія всередині кола показує його заголовок, який є напрямком, на який вказує боїд:

Грубий малюнок двох «боїд», один звернений ліворуч, а другий - праворуч.

  • Кут кожної бої (як описано Рейнольдсом) повинен бути повним 300 градусів. (не 360)
  • Початковий заголовок і положення кожного боїда повинні бути рівномірно випадковими (але насінними, щоб вихід все-таки визначався), а також положенням.
  • Якщо радіус бої дорівнює 1, то радіус сусідства повинен бути 3.
  • Кількість боїдів буде від 2 до 20.
  • Кількість кадрів буде коливатися від 1-5000
  • Анімація повинна відтворюватися з мінімум 10 мілісекундами на кадр і максимум на 1 секунду більше кількості боїдів. (2 боїди = 2 секунди на максимум кадру, 3 боїди = 3 секунди на максимум кадру та ін.)
  • Анімація на виході повинна становити щонайменше 5 радіусів боїда на 5 боді-радіусів, що вдвічі перевищує кількість боїдів. Отже, мінімальний розмір для 2-х боїдів був би 10-боді-радіусів на 10-ти радіусів, а мінімальний для 3-х боїдів був би 15-радіових боїдів на 15-ти радіових каналів тощо.
  • Радіус кожного боїда повинен бути не менше 5 пікселів і максимум 50 пікселів.
  • Швидкість кожного боїда повинна бути обмежена, щоб він не переміщувався більше ніж на 1/5 свого радіуса в одному кадрі.
  • Вихід повинен бути визначеним, щоб той самий вхід давав той самий вихід, якщо його запускати кілька разів.
  • Якщо боїд досягає кордону, він повинен повернутись на інший бік. Так само околиці навколо кожної боїди повинні також обертатися навколо кордонів.

Правила алгоритму

У цьому випадку кожен боїд має сектор навколо нього, що охоплює 300 градусів, по центру заголовка боїда. Будь-які інші боїди в цьому "мікрорайоні" вважаються "сусідами", або (щоб використовувати термін Рейнольдса) "зграями".

  1. Кожна боїда повинна регулювати свій заголовок, щоб уникнути зіткнень та підтримувати комфортну відстань одного боді-радіуса до своїх сусідів. (Це аспект алгоритму "Розмежування". Один радіус боїда можна обійти, але він повинен бути як гумка, відкинувшись на місце.)

  2. Кожен боїд повинен додатково коригувати його заголовок таким чином, щоб він був ближчим до середнього заголовка інших боїдів у його сусідстві, доки він не заважає першому правилу. (Це аспект алгоритму "Вирівнювання")

  3. Кожна боїда повинна повернути себе до середнього положення своїх флотмаків, доки це не спричинить зіткнення або суттєво не заважає другому правилу.

У своїй роботі з цього питання він пояснює це так:

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

  • Уникнення зіткнень: уникайте зіткнень із сусідськими флотманами
  • Збіг швидкості: спроба співставити швидкість із сусідськими флотками
  • Flock Centering: спроба триматися поруч із флеш-парками поблизу

Більш детальний опис руху:

  • Стандартна реалізація алгоритму Boids зазвичай робить обчислення для кожного з правил і об'єднує його разом.
  • Для першого правила боїд проходить список сусідніх боїдів у його сусідстві, і якщо відстань між собою та сусідом менше певного значення, до заголовка боїду застосовується вектор, що відштовхує боїд від сусіда.
  • Для другого правила, boid обчислює середній заголовок своїх сусідів і додає невелику частину (ми використаємо 1/10 у цьому виклику) різниці між його поточним заголовком та середнім заголовком до його поточного заголовка.
  • Для третього і остаточного правила, боїд усереднює позиції своїх сусідів, обчислює вектор, який вказує на цю локацію. Цей вектор помножується на ще меншу кількість, ніж те, що було використано для правила 2 (для цього завдання буде використано 1/50) та застосовано до заголовка.
  • Потім боїд переміщують у напрямку свого заголовка

Ось корисна реалізація псевдокоду Алгоритму Бойдса.

Приклад введення та виведення

Вхід:

5, 190 (5 косинок, 190 кадрів)

Вихід:

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

Критерій виграшу

Це , тому найменше рішення в байтах виграє.


7
"Звичайно, в алгоритмі є більше, тому я дуже рекомендую перевірити джерело." - тут все необхідне чи ні? Якщо ні, то я б рекомендував це виправити.
Джонатан Аллан

1
Будь ласка, використовуйте пісочницю перед публікацією викликів, як зазначено на сторінці запиту .
недолік

@JonathanAllan Так, тут все необхідне, але в джерелі доступні більш глибокі пояснення, які можуть мати більше сенсу для інших користувачів.
iPhoenix

11
Це цікавий виклик (мені здається, що стихійні форми поведінки є захоплюючими), але його потрібно буде чітко вказати, особливо для коду-гольфу, інакше тиск для зменшення довжини коду призведе до можливого відхилення від духу виклику до бути стимульованим.
трихоплакс

Відповіді:


7

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

-1 байт від Джонатана Фреха
+11 байт, щоб краще відповідати характеристиці
-2 байти від Кевіна Круїйссена
-12 байт для зміни аргів на t ()
+29 байт, тому що я робив привид неправильно, див. Коментовану версію нижче
-40 байт для використання для циклів замість окремих викликів для кожного привид
-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:

боїди

Або та сама анімація, але показує сусідство кожного боїда.


1
Не можете 2*PIстати TAUдля збереження байта?
Джонатан Фрех

@JonathanFrech Так, це може; Спочатку я мав -I, PI, і я йшов цим шляхом, але я потрапив убік.
Фларкс

Моя програма (яка була написана у js та html) не експортувала gif, але вона намалювала зображення, і я застосував програму захоплення екрана та перетворив експортоване відео у gif. Однак я помітив одну річ. У боїдів є синій контур, який не відповідає специфікації :)
iPhoenix

Це ще одне доброзичливе нагадування, ця відповідь не відповідає специфікації, тому вона не отримає щедрості.
iPhoenix

1
Я не знаю, що обробляє, але я думаю, що ви можете пограти в наступні речі: ,i,до, ,i=0,а потім видаліть i=0внутрішню петлю for. (-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 за допомогою програму scheentogif.com .
Kevin Cruijssen

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>


Здається, боїди не взаємодіють, коли я запускаю фрагмент
Джо Кінг,

@JoKing це слід виправити зараз
Патрік Робертс

Проблема полягала в тому, що міні-міністр babel затіняв глобальну змінну в одній функції з назвою параметра, а неявна набір тексту на номер не видав помилки, тому функція просто мовчки вийшла з ладу і ніколи не виявляла жодного сусіда.
Патрік Робертс

Я спробую зробити інтерактивну демонстрацію завтра ввечері, але у мене сьогодні не вистачає пари.
Патрік Робертс

Лише зауваження: де воно читається t.a+v+l/10+f/50, якщо ви зміните це на t.a+v/3+l/10+f/50, воно створює дещо цікавішу поведінку, але поточна програма менша і все-таки специфічна.
Патрік Робертс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.