Boids 알고리즘 구현


18

소개

Boids 알고리즘 그룹의 발적 행동의 비교적 간단한 데모입니다. 제작자 Craig Reynolds가 설명하는 세 가지 주요 규칙이 있습니다.

기본 플록 킹 모델은 세 가지 간단한 조향 동작으로 구성되며, 주변 플록 메이트의 위치와 속도에 따라 개별 보이드가 어떻게 기동하는지 설명합니다.

  • 분리 : 지역 무리 무리가 몰리지 않도록 조향하십시오.
  • 정렬 : 현지 무리의 평균 방향으로 조정합니다.
  • 응집력 : 지역 무리 무리의 평균 위치를 향해 움직입니다.

각 보이드는 전체 장면의 기하학적 설명에 직접 액세스 할 수 있지만, 무리를 짓기 위해서는 주변의 특정 지역에있는 무리에게만 반응해야합니다. 이웃은 거리 (보이드의 중심에서 측정)와 각도로 특징 지어 집니다 보이드의 비행 방향으로부터 으로한다. 이 지역 주변의 무리는 무시됩니다. 이 동네는 한정된 지각의 모델로 여겨 질 수 있지만 (어렴 한 물에서 물고기에 의해), 무리가 보이드 스티어링에 영향을 미치는 영역을 정의하는 것으로 생각하는 것이 더 낫습니다.

설명 할 때 완벽하지 않으므로 소스를 확인하는 것이 좋습니다. . 그는 또한 그의 사이트에 매우 유익한 사진을 가지고 있습니다.

도전

보이드 수 (시뮬레이션 된 엔티티)와 프레임 수를 고려하여 시뮬레이션의 애니메이션을 출력합니다.

  • 구멍은 빨간색 원으로 렌더되어야하며, 원 안에 선이 머리글을 나타내는 방향으로 표시되어야합니다.

하나는 왼쪽을 향하고 다른 하나는 오른쪽을 향하는 두 개의 "구멍"의 조잡한 그림.

  • 각 구멍의 각도 (레이놀즈에 의해 기술 된 바와 같이)는 최대 300도 여야합니다. (360 아님)
  • 각 보이드의 시작 방향과 위치는 위치뿐만 아니라 균일하게 무작위이어야합니다 (그러나 출력은 여전히 ​​결정되도록 시드됩니다).
  • 보이드의 반경이 1이면 이웃의 반경이 3이어야합니다.
  • 보이드의 수는 2-20 사이입니다.
  • 프레임 수는 1-5000 사이입니다.
  • 애니메이션은 프레임 당 최소 10 밀리 초, 보이드 수의 최대 1 초로 재생되어야합니다. (2 보이드 = 프레임 최대 2 초, 3 보이드 = 프레임 최대 3 초 등)
  • 출력 애니메이션은 보이드 수의 절반에 5 곱하기 5 곱하기 반지름을 곱한 것이어야합니다. 따라서, 2 개의 보이드의 최소 크기는 10 보이드 반경 x 10 보이드 반경이고, 3 개의 보이드의 최소 크기는 15 보이드 반경 x 15 보이드 반경입니다.
  • 각 보이드의 반경은 최소 5 픽셀, 최대 50 픽셀이어야합니다.
  • 각 보이드의 속도는 한 프레임에서 반경의 1/5 이상 이동하지 않도록 제한해야합니다.
  • 여러 번 실행하는 경우 동일한 입력이 동일한 출력을 생성하도록 출력을 결정해야합니다.
  • 구멍이 경계에 도달하면 반대쪽으로 다시 감싸 야합니다. 마찬가지로, 각 보이드 주변은 또한 테두리를 감싸 야합니다.

알고리즘 규칙

이 경우, 각 보이드는 보이드의 방향을 중심으로 300도에 걸쳐 섹터를 가지고 있습니다. 이 "이웃"에있는 다른 모든 보이드는 "이웃"또는 (레이놀즈의 용어를 사용하기 위해) "사냥개"로 간주됩니다.

  1. 각 보이드는 충돌을 피하고 인접 보이드와 하나의 보이드 반경이 편안한 거리를 유지하도록 방향을 조정해야합니다. (이것은 알고리즘의 "분리"측면입니다. 하나의 구멍 반경은 우회 될 수 있지만, 제자리로 돌아가는 고무 밴드와 같아야합니다.)

  2. 각 보이드는 첫 번째 규칙을 방해하지 않는 한 이웃의 다른 보이드의 평균 헤딩에 더 근접하도록 헤딩을 추가로 조정해야합니다. (이것은 알고리즘의 "정렬"측면입니다)

  3. 충돌이 발생하지 않거나 두 번째 규칙을 크게 방해하지 않는 한 각 보이드는 플록 메이트의 평균 위치를 향해 회전해야합니다.

에서 주제에 자신의 논문 은 다음과 같이, 그는이 설명 :

시뮬레이션 된 무리를 만들기 위해 기하학적 비행을 지원하는 보이드 모델로 시작합니다. 우리는 충돌 회피의 반대 세력과 무리에 합류하려는 충동에 해당하는 행동을 추가합니다. 간단히 규칙으로 말하고 우선 순위를 낮추는 순서로 시뮬레이션 된 무리를 일으키는 동작은 다음과 같습니다.

  • 충돌 방지 : 근처의 무리와 충돌 방지
  • Velocity Matching : 주변 무리와 속도를 맞추려고 시도
  • 무리 센터링 : 근처 무리 무리와 가까이 있으십시오

움직임에 대한 자세한 설명 :

  • Boids 알고리즘의 표준 구현은 일반적으로 각 규칙에 대한 계산을 수행하고이를 병합합니다.
  • 첫 번째 규칙의 경우, 보이드는 주변의 이웃 보이드 목록을 통과하며, 자신과 이웃 사이의 거리가 특정 값보다 작은 경우, 보이드를 멀어 지도록 밀어내는 벡터가 보이드의 제목에 적용됩니다.
  • 두 번째 규칙의 경우, boid는 이웃의 평균 머리글을 계산하고 현재 머리글과 현재 머리글의 평균 머리글 사이의 차이의 작은 부분 (이 과제에서는 1/10을 사용함)을 추가합니다.
  • 세 번째이자 마지막 규칙의 경우, 보이드는 이웃의 위치를 ​​평균화하고이 위치를 가리키는 벡터를 계산합니다. 이 벡터는 규칙 2에 사용 된 것 (이 챌린지에 1/50이 사용됨)보다 ​​훨씬 적은 수를 곱하여 제목에 적용됩니다.
  • 보이드는 다음 방향으로 이동

여기Boids 알고리즘의 유용한 의사 코드 구현은 .

입력 및 출력 예

입력:

5, 190 (5 개, 190 개 프레임)

산출:

5 개의 boid를 가진 Boids 알고리즘의 190 프레임 애니메이션.

승리 기준

이것은 이므로 바이트 단위의 가장 작은 솔루션이 이깁니다.


7
"물론 알고리즘에는 더 많은 것이 있으므로 소스를 확인하는 것이 좋습니다." -필요한 모든 것이 있습니까? 그렇지 않은 경우 수정하는 것이 좋습니다.
Jonathan Allan

1
요청 페이지 에 나와있는대로 챌린지를 게시하기 전에 샌드 박스 를 사용 하십시오 .
flawr

@JonathanAllan 예, 필요한 모든 것이 여기 있지만 다른 사용자에게 더 이해가 될 수있는보다 자세한 설명은 소스에서 확인할 수 있습니다.
iPhoenix

11
이것은 흥미로운 도전입니다 (나는 무리 짓는 행동이 매력적이라고 ​​생각합니다), 특히 코드 골프에 대해 잘 지정해야합니다. 그렇지 않으면 코드의 길이를 줄이는 압력이 도전 정신에서 가능한 모든 편차를 유발합니다. 인센티브를 받는다.
trichoplax

답변:


7

처리 3.3.6 (자바) ,932 931 940 928 957 917 904 바이트

-1에서 바이트 조나단 FRECH
더 나은에 11 바이트가 일치 사양
-2에서 바이트 케빈 Cruijssen
t에 인수 () 변경 -12 바이트
내가 잘못 고스트하고 있었기 때문에, 29 바이트 이하 버전을 주석 참조
에 사용 -40 바이트
기본 프레임 레이트를 사용 하기 위해 각 고스트에 대해 별도의 호출 대신 루프 -13 바이트

글쎄, 그것은 자바 골프를하지 않는 사람을위한 시작입니다. :)

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

처리에서 입력을 수행하는 합리적인 방법을 모르므로 처음 두 변수는 입력입니다 (그리고 바이트 수에 대한 값 (5 바이트)는 계산하지 않았습니다). 이것이 문제라면 다른 것을 시도해 볼 수 있습니다.

나 자신을 호스팅하지 않고 온라인으로 시도해 볼 수있는 좋은 방법을 모른다. 그리고 그것은 내가 시도하고 싶지 않은 것입니다. 내가 할 수있는 영리한 것이 있으면 알려주세요.

주석이있는 형식화 된 코드

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?
Jonathan Frech

@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와 상당히 유사 해 보이기 때문에 그것이 가능하다고 생각합니다. 또한 screentogif.com 으로 gif를 만들 수 있습니다.
Kevin Cruijssen

4

자바 스크립트 (ES6) + HTML5, 1200 바이트

Canvas API를 사용하는 현재 솔루션은 다음과 같습니다. 이 eval()함수는 첫 번째 입력이 채우기 Boid이고 두 번째가 애니메이션 프레임의 수인 카레 함수를 반환합니다 . Infinity연속 애니메이션에 사용할 수 있습니다 .

eval(...)1187 바이트이고 <canvas id=c>CSS의이 불필요 (1200)의 총을, 13 바이트이지만 편의를 위해, 당신이 캔버스의 가장자리를 볼 수 있습니다.

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>


스 니펫을 실행할 때 구멍이 상호 작용하지 않는 것 같습니다
Jo King

지금은 수정되어야 @JoKing
패트릭 로버츠에게

문제는 babel minifier가 하나의 함수에서 매개 변수 이름으로 전역 변수를 음영 처리하고 숫자에 대한 암시 적 유형 캐스트에서 오류가 발생하지 않으므로 함수가 자동으로 실패하고 이웃을 감지하지 못했기 때문입니다.
패트릭 로버츠

나는 내일 밤에 대화 형 데모를 만들려고 노력하지만 오늘 밤에는 증기가 부족합니다.
Patrick Roberts

참고 사항 : 읽은 곳에서로 t.a+v+l/10+f/50변경 t.a+v/3+l/10+f/50하면 다소 흥미로운 동작이 발생하지만 현재 프로그램은 더 작고 사양에 맞습니다.
패트릭 로버츠
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.