Boids algoritmasını uygulama


18

Giriş

Boids Algoritması Bir gruptaki acil davranış nispeten basit bir göstergesidir. Yaratıcısı Craig Reynolds tarafından açıklandığı gibi üç ana kuralı vardır:

Temel akın modeli, tek bir örgülü manevraların, yakın akrabalarının pozisyonlarına ve hızlarına göre nasıl hareket ettiğini tanımlayan üç basit direksiyon davranışından oluşur:

  • Ayrılma : yerel sürüleri önlemek için yönlendirin.
  • Hizalama : yerel sürü arkadaşlarının ortalama pozisyonuna yönelir.
  • Uyum : yerel sürü arkadaşlarının ortalama konumuna doğru yönelmek.

Her bir satın alma, tüm sahnenin geometrik açıklamasına doğrudan erişime sahiptir, ancak akın, yalnızca etrafındaki belirli bir küçük mahalledeki akın arkadaşlarına tepki vermesini gerektirir. Mahalle, (uçuşun merkezinden ölçülen) bir mesafe ve borunun uçuş yönünden ölçülen bir açı ile karakterize edilir . Bu yerel mahallenin dışındaki sürü arkadaşları yok sayılır. Komşuluk sınırlı bir algı modeli olarak düşünülebilir (bulanık sudaki balıklar gibi), ancak bunu akın arkadaşlarının bir ihale yönlendirmesini etkilediği bölgeyi tanımlamak olarak düşünmek daha doğrudur.

Bir şeyleri açıklarken mükemmel değilim, bu yüzden kaynağı kontrol etmenizi tavsiye ederim . Ayrıca sitesinde süper bilgilendirici resimler var.

Meydan okuma

Teklif sayısı (simüle edilen varlıklar) ve kare sayısı göz önüne alındığında, simülasyonun bir animasyonunu çıktılayın.

  • Boids, dairenin içindeki bir çizgi başlığını gösteren, boid'in işaret ettiği yön olan kırmızı bir daire olarak oluşturulmalıdır:

Biri sola, diğeri sağa bakacak şekilde iki "kuşağın" kaba çizimi.

  • Her bir örgünün açısı (Reynolds tarafından açıklandığı gibi) tam 300 derece olmalıdır. (360 değil)
  • Her bir alımın başlangıç ​​pozisyonu ve pozisyonu, aynı zamanda pozisyonun yanı sıra homojen olarak rastgele olmalıdır (ancak tohumlanmış, böylece çıktı hala belirlenebilir).
  • Boid yarıçapı 1 ise, mahallenin yarıçapı 3 olmalıdır.
  • Teklif sayısı 2-20 arasında herhangi bir yerde olacaktır.
  • Kare sayısı 1-5000 arasında herhangi bir yerde olacaktır
  • Animasyon, çerçeve başına minimum 10 milisaniye ve teklif sayısının maksimum 1 saniyesi ile oynatılmalıdır. (2 teklif = maksimum çerçeve başına 2 saniye, 3 teklif = maksimum çerçeve başına 3 saniye, vb)
  • Çıktı animasyonu en az 5 boid-radii x 5 boid-radii, boid sayısının yarısı kadardır. Bu nedenle, 2 teklif için minimum boyut 10 boidian yarıçapı ile 10 boid-yarıçap, 3 boid yarıçapı için 15 boid yarıçap, et cetera olacaktır.
  • Her bir örgünün yarıçapı en az 5 piksel ve en fazla 50 piksel olmalıdır.
  • Her çerçevenin hızı, yarıçapının 1 / 5'inden fazlasını tek bir çerçevede hareket etmeyecek şekilde sınırlanmalıdır.
  • Çıktı belirlenmelidir, böylece aynı girdi birden çok kez çalıştırılırsa aynı çıktıyı üretir.
  • Bir sınır bir sınıra ulaşırsa, diğer tarafa geri sarılmalıdır. Benzer şekilde, her bir bölgenin etrafındaki mahalle de sınırların etrafına sarılmalıdır.

Algoritma kuralları

Bu durumda, her bir borcun etrafında, borunun istikametini merkez alan 300 dereceyi kapsayan bir sektör vardır. Bu "mahalledeki" diğer tüm teklifler "komşular" veya (Reynolds'un terimini kullanmak için) "sürü arkadaşları" olarak kabul edilir.

  1. Her bir teklif, çarpışmalardan kaçınmak için yönünü ayarlamalı ve komşularıyla birlikte bir sınır yarıçapı arasında rahat bir mesafe bırakmalıdır. (Bu, algoritmanın "Ayırma" yönüdür. Tek yarıçapı atlanabilir, ancak yerine oturan bir lastik bant gibi olmalıdır.)

  2. Her bir teklif, ilk kurala müdahale etmediği sürece, başlığını mahallesindeki diğer tekliflerin ortalama pozisyonuna daha yakın olacak şekilde ayarlamalıdır. (Bu algoritmanın "Hizalama" yönü)

  3. Her bir çarpışma, çarpışmaya neden olmadığı veya ikinci kurala önemli ölçüde müdahale etmediği sürece, sürü arkadaşlarının ortalama konumuna doğru dönmelidir.

Konuyla ilgili makalesinde bunu şöyle açıklıyor:

Simüle edilmiş bir sürü oluşturmak için geometrik uçuşu destekleyen bir modelle başlıyoruz. Çarpışmadan kaçınma karşıt güçlerine ve sürüye katılma isteğine karşılık gelen davranışlar ekliyoruz. Kısaca kural olarak belirtilir ve azalan önceliğe göre, simüle edilmiş akınlara neden olan davranışlar şunlardır:

  • Çarpışmadan Kaçınma: Yakındaki sürü arkadaşlarıyla çarpışmalardan kaçının
  • Hız Eşleme: hızı, yakın sürü arkadaşlarıyla eşleştirmeye çalışın
  • Flok Centering: Flock arkadaşlarına yakın kalmaya çalışın

Hareketin daha ayrıntılı açıklaması:

  • Boids Algoritmasının standart uygulaması genellikle kuralların her biri için bir hesaplama yapar ve onu birleştirir.
  • İlk kural için, satın alma, komşusu içindeki komşu kazanlar listesinden geçer ve eğer kendisi ile komşu arasındaki mesafe belirli bir değerden daha azsa, sınırı komşu olan bir vektör, komitenin başlığına uygulanır.
  • İkinci kural için, teklif komşularının ortalama pozisyonunu hesaplar ve mevcut pozisyonu ile mevcut pozisyonu arasındaki ortalama pozisyon arasındaki farkın küçük bir kısmını (bu zorlukta 1/10 kullanacağız) ekler.
  • Üçüncü ve son kural için, komşularının konumlarını ortalar, bu konumu işaret eden bir vektör hesaplar. Bu vektör, kural 2 için kullanılandan daha da küçük bir sayı ile çarpılır (bu zorluk için 1/50 kullanılır) ve başlığa uygulanır.
  • Daha sonra bobin istikameti yönünde hareket eder

İşte Boids Algoritmasının yararlı bir sözde kod uygulaması.

Örnek Giriş ve Çıkış

Giriş:

5190 (5 boids, 190 çerçeve)

Çıktı:

5 çerçeveli Boids Algoritmasının 190-kare animasyonu.

Kazanan kriter

Bu , bu yüzden bayttaki en küçük çözüm kazanır.


7
"Tabii ki, algoritma için daha fazlası var, bu yüzden kaynağı kontrol etmenizi tavsiye ederim." - burada her şey gerekli mi değil mi? Değilse bunu düzeltmenizi tavsiye ederim.
Jonathan Allan

1
Soru sayfasında önerildiği gibi, zorlukları göndermeden önce lütfen korumalı alanı kullanın .
Kusur

@JonathanAllan Evet, gerekli her şey burada, ancak diğer kullanıcılar için daha anlamlı olabilecek daha ayrıntılı açıklamalar kaynakta mevcuttur.
iPhoenix

11
Bu ilginç bir meydan okumadır (akın davranışlarını büyüleyici buluyorum), ancak özellikle bir kod golfü için iyi belirtilmesi gerekecek, aksi takdirde kodun uzunluğunu azaltma baskısı, mücadelenin ruhundan her olası sapmaya neden olacaktır. teşvik edilmek.
trichoplax

Yanıtlar:


7

İşleme 3.3.6 (Java) ,932 931 940 928 957 917 904 bayt

-1 bayt Jonathan Frech
daha iyi + 11 bayt eşleşen spec
-2 gelen bayt Kevin Cruijssen
t bağımsız değişken () değiştirmek için -12 bayt
yanlış gölgelenme yaptığı için, +29 bayt aşağıdaki versiyonu yorumunu bkz
kullanılarak -40 bayt
varsayılan frameRate'i kullanmak için her hayalet için -13 bayt yerine ayrı aramalar yerine döngüler , 30

Java-golf yapmayan biri için bu bir başlangıç. :)

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));}}

İşlemde girdi yapmak için makul bir yol bilmiyorum, bu yüzden ilk iki değişken girdi (ve bayt sayısına doğru değerlerini (5 bayt) saymadım). Bu bir sorunsa, başka şeyler deneyebilirim.

Ayrıca kendimi bir şey barındırmadan çevrimiçi denemek için izin vermek için iyi bir yol bilmiyorum (Processing.js projesi bu kod stili ile baş edemez); ve bu, yapmaya hevesli olmadığım bir şey. Akıllıca yapabileceğim bir şey varsa bana bildirin.

Biçimlendirilmiş kod, yorumlarla birlikte

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));
  }
}

Örnek çıktı

n = 15, çerçeveler = 400:

Boids

Veya aynı animasyon, ancak her bir satın alma mahallesini gösteriyor.


1
Can 2*PIolmazlar TAUbir byte kaydetmek için?
Jonathan Frech

@JonathanFrech Evet yapabilir; Aslında -PI, PI vardı ve ben bu şekilde gidiyordum, ama kaldırıldı.
Phlarx

Programım (js ve html ile yazılmış) bir gif vermedi, ancak bir resim çekti ve bir ekran yakalama programı kullandım ve ihraç ettiği videoyu bir gif'e dönüştürdüm. Yine de fark ettiğim bir şey var. Boids mavi özelliği var, bu şartnameye
uymuyor

Sadece başka bir dost hatırlatma, bu cevap özellikleri takip etmez, bu yüzden ödül alamaz.
iPhoenix

1
Ben İşleme bilmiyorum ama golf şunları yapabilirsiniz düşünüyorum: ,i,etmek ,i=0,ve daha sonra kaldırmak i=0için-döngü içinde. (-1 bayt); frameCount%f==0- frameCount%f<1(1 byte); &&için &son eğer 2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6(-1 byte). Yine, bunların mümkün olup olmadığından emin değilim, ancak İşleme Java'ya oldukça benzediğinden, bence öyle. Ayrıca, screentogif.com ile bir gif oluşturmayı deneyebilirsiniz .
Kevin Cruijssen

4

JavaScript (ES6) + HTML5, 1200 bayt

Canvas API'yi kullanarak şu anki çözümüm. eval(), İlk girişi curried fonksiyonu geri Boidpopülasyon ve ikinci animasyon çerçeve sayısıdır. InfinitySürekli animasyon için kullanabilirsiniz .

eval(...)1187 bayt ve <canvas id=c>CSS gereksizdir 1200 da eklendiğinde toplam 13 byte, ancak kolaylık olması için, bu tuval kenarlarını görmenizi sağlar.

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>

Düzenle

İstendiği gibi, Boid nüfus için girişi olan başka bir snippet:

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>


Parçacığı çalıştırdığımda teklifler etkileşimli görünmüyor
Jo King

Şimdi düzeltilmesi gerekiyor
Patrick Roberts

Sorun, babel minifier'ın bir işlevde bir parametre adı ile küresel bir değişkeni gölgelemesi ve bir sayıya örtülü yazım hatası bir hata atmadığı için işlev sessizce başarısız oldu ve hiçbir komşu tespit etmedi.
Patrick Roberts

Yarın gece interaktif bir demo yapmaya çalışacağım ama bu gece için buharım tükendi.
Patrick Roberts

Sadece bir not: okuduğu yerde t.a+v+l/10+f/50, bunu değiştirirseniz t.a+v/3+l/10+f/50, biraz daha ilginç bir davranış üretir, ancak mevcut program daha küçüktür ve hala belirtilebilir.
Patrick Roberts
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.