스프로킷 과학 : 체인 드라이브 시스템 애니메이션 교차하지 않도록 스프라켓을 통과해야 합니다. . 예를 들어, 위

이러한 문제의 목적은 애니메이션의 생성하는 체인 드라이브 의 집합으로 구성된 시스템, 스프라켓 기어 a로 함께 연결 체인 .

일반적인 요구 사항

프로그램에는 삼중 항으로 지정된 스프로킷 목록 이 제공 됩니다 (x, y, radius). 얻어진 체인 구동 장치는 a로 함께 연결이 스프로킷 구성되는 밀폐 체인 긴장 , 그들 각각을 통해 전달 하기 . 당신의 목표는 시스템을 움직이는 무한 루프 애니메이션 을 만드는 것입니다 . 예를 들어, 입력이 주어지면

(0, 0, 16),  (100, 0, 16),  (100, 100, 12),  (50, 50, 24),  (0, 100, 12)

출력은 다음과 같아야합니다.

.

좌표계 오른쪽 x 축 지점 및 y 축가 가리키는 것이어야한다. 반경이 8보다 크거나 같은 숫자 라고 가정 할 수 있습니다 (나중에 중요한 이유를 볼 것입니다). 두 개 이상의 스프로킷 이 있고 스프로킷이 서로 교차하지 않는다고 가정 할 수도 있습니다 . 단위입력의 너무 중요하지 않습니다. 이 게시물의 모든 예제와 테스트 사례는 픽셀을 입력 단위로 사용하므로 (예를 들어, 앞 그림에서 중간 스프로킷의 반경은 24 픽셀입니다.) 이러한 단위에서 너무 많이 벗어나지 마십시오. 나머지 과제에서 공간 수량은 입력과 동일한 단위로 제공되는 것으로 이해됩니다. 비율을 올바르게 유지하십시오! 출력의 크기는 전체 시스템이 볼 수 있도록 충분히 큰 모든 톱니의 경계 상자보다 약간 더 큰해야한다. 특히, 스프로킷의 절대 위치는 출력에 영향을 미치지 않아야합니다. 상대 위치 만 사용해야합니다 (예를 들어, 위의 예에서 모든 스프라켓을 같은 양만큼 이동하면 출력은 동일하게 유지됩니다).

체인은해야 접선 은 접점의 모든 지점에서 통과 스프라켓, 그리고 바로 다른 곳. 체인은 인접한 체인 세그먼트 (즉, 동일한 스프라켓에서 만나는 두 스프라켓 사이의 체인 부분) 서로 교차하지 않도록 스프라켓을 통과해야 합니다.

.

예를 들어, 위 왼쪽 시스템은 유효하지만 왼쪽 아래 스프라켓을 지나는 두 개의 인접한 체인 세그먼트가 교차하기 때문에 가운데 시스템은 유효하지 않습니다. 그러나 두 개의 교차 체인 세그먼트가 인접하지 않기 때문에 올바른 시스템 유효합니다 (이 시스템은 다른 두 개의 체인 세그먼트와는 다른 입력으로 생성됩니다).

일을 단순하게하기 위해 (r) 스프라켓 이 두 개의 인접한 스프라켓의 볼록 껍질 또는 각 이웃과 다른 이웃의 볼록 껍질과 교차하지 않는다고 가정 할 수 있습니다. 다시 말해, 아래 다이어그램의 상단 스프로킷은 음영 처리 된 영역과 교차하지 않을 수 있습니다.

체인 세그먼트가 지나가는 스프라켓 (예 : 마지막 테스트 케이스) 이외의 스프라켓과 교차 할 수 있습니다. 이 경우 체인은 항상 스프라켓 앞에 나타나야합니다.

시각적 요구 사항

체인은 폭이 교대 로 연결된 일련의 링크 로 구성되어야합니다 . 좁은 링크 의 너비는 약 2이어야하고 넓은 링크 의 너비는 약 5 여야합니다. 두 유형의 링크 의 길이 는 거의 같아야합니다. 기간체인의 길이, 즉 넓은 / 좁은 링크 쌍의 총 길이는 체인 길이의 정수배에 맞는 4π에 가장 가까운 숫자 여야합니다. 예를 들어, 체인의 길이가 1,000 인 경우주기는 12.5 여야하며 이는 1,000의 정수 횟수 (80)에 맞는 4π (12.566 …)에 가장 가까운 숫자입니다. 체인이 감싸는 지점에 아티팩트가 없도록주기가 체인 길이에 정수 횟수만큼 맞는 것이 중요합니다.

반경 R 의 스프로킷은 3 개의 동심원으로 구성되어야합니다. 중심 축은 약 3의 반경 원이어야합니다. 스프로킷의 신체 에 대한 반경의 원이어야 축 주위에 R – 4.5; 그리고 몸통 주위 의 스프라켓 림 은 약
R -1.5 반경의 원이어야합니다 . 림에는 스프로킷의 톱니 도 포함 해야하며 너비는 약 4입니다. 톱니의 크기와 간격은 체인 링크의 크기와 일치해야 깔끔하게 맞 물릴 수 있습니다.

스프로킷의 톱니주기, 즉 스프로킷의 원주를 따라 두 개의 연속 톱니 사이의 거리는 체인의주기와 일치해야합니다. 주기가 약 4π이고 스프로킷의 반지름이 고르기 때문에,주기는 스프로킷의 원주에 거의 정수의 횟수로 맞아야하기 때문에 그 지점에 눈에 띄는 인공물이 없어야합니다. 스프로킷의 이빨이 around니다.

체인, 스프라켓의 다른 부분 및 배경을 쉽게 구분할 수있는 한 색상 조합을 사용할 수 있습니다 . 배경이 투명 할 수 있습니다. 이 포스트의 예제 는 체인, 스프로킷의 액슬 및 림 및 스프로킷의 바디에 사용됩니다. #202020 #868481 #646361

애니메이션 요구 사항

입력 목록 의 첫 번째 스프라켓시계 방향으로 회전해야합니다 . 나머지 스프라켓은 그에 따라 회전해야합니다. 체인은 초당 약 16π (약 50) 단위 의 속도 로 움직여야합니다. 프레임 속도는 사용자에게 달려 있지만 애니메이션은 충분히 매끄럽게 보입니다.

애니메이션은 매끄럽게 반복 되어야 합니다 .

적합성

시각적 속성 및 비율 중 일부는 의도적으로 대략적으로 만 지정되므로 정확하게 일치시킬 필요는 없습니다 . 프로그램의 출력은 여기에 주어진 예제의 픽셀 간 복제 일 필요는 없지만 비슷하게 보일 것입니다. 특히, 체인과 스프로킷의 정확한 비율과 체인 링크와 스프로킷의 톱니의 정확한 모양은 유연합니다.

따라야 할 가장 중요한 사항은 다음과 같습니다.

  • 체인은 올바른 방향에서 입력 순서대로 스프라켓을 통과해야합니다.
  • 체인은 모든 접촉 지점에서 스프라켓에 접해야합니다.
  • 체인의 링크와 스프로킷의 톱니는 최소한 정확한 간격과 위상까지 깔끔하게 메워 져야합니다.
  • 체인의 링크와 스프로킷의 톱니 사이의 간격은 랩핑 지점에서 눈에 띄는 아티팩트가 없도록해야합니다.
  • 스프로킷은 올바른 방향으로 회전해야합니다.
  • 애니메이션은 매끄럽게 반복되어야합니다.

마지막으로, 기술적으로,이 도전의 목표는 창의력을 발휘하고보다 정교한 결과물을 생성하고 싶을 때 가장 짧은 코드를 작성하는 것입니다.

도전

위에서 설명한대로 스프로킷 목록을 가져 와서 해당 체인 구동 시스템 애니메이션을 생성 하는 프로그램 또는 함수를 작성하십시오 .

입력과 출력

명령 행 을 통해, STDIN을 통해 , 함수 인수 로 또는 동등한 방법을 사용하여 입력 을 받을 수 있습니다 . 입력에 편리한 형식 을 사용할 수 있지만 게시물에 입력해야합니다.

으로 출력 , 당신은 할 수 있습니다 직접 애니메이션을 표시 , 생산 애니메이션 파일을 (예를 들어, 애니메이션 GIF)를, 또는 생산 프레임 파일의 순서를 (단,이 경우 작은 벌금있다;. 아래 참조) 파일 출력을 사용하는 경우는, 확인 할 프레임 수는 합리적이다 (이 글의 예제는 거의 프레임을 사용) 프레임의 수를 최소화 할 필요가 없습니다,하지만 당신은 너무 많은 불필요한 프레임을 생성해서는 안된다. 일련의 프레임을 출력하는 경우 게시물에 프레임 속도지정하십시오 .

점수

이것은 code-golf 입니다. 짧은 대답은 , 바이트, 승리.

+ 10 % 페널티
프로그램에서 애니메이션을 직접 표시하거나 단일 애니메이션 파일을 생성하는 대신 프레임 시퀀스를 출력으로 생성하는 경우 점수에 10 %를 추가하십시오.

테스트 사례

시험 1

(0, 0, 26),  (120, 0, 26)

시험 2

(100, 100, 60),  (220, 100, 14)

시험 3

(100, 100, 16),  (100, 0, 24),  (0, 100, 24),  (0, 0, 16)

시험 4

(0, 0, 60),  (44, 140, 16),  (-204, 140, 16),  (-160, 0, 60),  (-112, 188, 12),
(-190, 300, 30),  (30, 300, 30),  (-48, 188, 12)

시험 5

(0, 128, 14),  (46.17, 63.55, 10),  (121.74, 39.55, 14),  (74.71, -24.28, 10),
(75.24, -103.55, 14),  (0, -78.56, 10),  (-75.24, -103.55, 14),  (-74.71, -24.28, 10),
(-121.74, 39.55, 14),  (-46.17, 63.55, 10)

시험 6

(367, 151, 12),  (210, 75, 36),  (57, 286, 38),  (14, 181, 32),  (91, 124, 18),
(298, 366, 38),  (141, 3, 52),  (80, 179, 26),  (313, 32, 26),  (146, 280, 10),
(126, 253, 8),  (220, 184, 24),  (135, 332, 8),  (365, 296, 50),  (248, 217, 8),
(218, 392, 30)




답변

자바 스크립트 (ES6), 2557 1915 1897 1681 바이트

이것은 슈퍼 듀퍼 골프 가 아닙니다 . 부분적으로 손으로 축소되었지만 특별한 것은 아닙니다. 축소하기 전에 더 골프를 쳤다면 틀림없이 더 짧을 수는 있지만 이미 이것에 충분한 시간을 보냈습니다.

편집 : 좋습니다. 그래서 더 많은 시간을 보냈고 축소하기 전에 코드를 더 많이 사용했습니다 (매우 수동으로). 코드는 여전히 동일한 접근 방식과 전체 구조를 사용하고 있지만 여전히 642 바이트를 절약했습니다. 내가 그렇게 말하면 너무 초라하지 않습니다. 아마도 바이트 절약 기회를 놓친 것 같지만이 시점에서도 더 이상 작동하지 않습니다. 출력면에서 다른 유일한 점은 더 간결하게 쓸 수있는 약간 다른 색상을 사용한다는 것입니다.

편집 2 (많은 나중에) : 18 바이트를 저장했습니다. ConorO’Brien에게 감사의 말을 전합니다.

편집 3 : 솔직히 코드를 리버스 엔지니어링 할 것이라고 생각했습니다. 솔직히 말해서 어떻게했는지 기억이 나지 않았고 ungolfed 버전을 잃어 버렸습니다. 그래서 나는 갔고, lo와 보라는 구조 조정과 약간의 골프를함으로써 절약 할 또 다른 316 바이트를 발견했습니다.

R=g=>{with(Math){V=(x,y,o)=>o={x,y,l:sqrt(x*x+y*y),a:v=>V(x+v.x,y+v.y),s:v=>o.a(v.m(-1)),m:f=>V(x*f,y*f),t:r=>V(x*cos(r)-y*sin(r),x*sin(r)+y*cos(r)),c:v=>x*v.y-y*v.x,toString:_=>x+','+y};a='appendChild',b='setAttribute';S=(e,a)=>Object.keys(a).map(n=>e[b](n,a[n]))&&e;T=(t,a)=>S(k.createElementNS('http://www.w3.org/2000/svg',t),a);C=(e,a)=>S(e.cloneNode(),a);P=a=>T('path',(a.fill='none',a));w=h=-(x=y=1/0);G=g.map((a,g)=>(g=V(...a))&&(u=(g.r=a[2])+5,x=min(x,g.x-u),y=min(y,g.y-u),w=max(w,g.x+u),h=max(h,g.y+u))&&g);k=document;I=k[a].bind(k.body[a](T('svg',{width:w-x,height:h-y}))[a](T('g',{transform:`translate(${-x},${h})scale(1,-1)`})));L=(c)=>(h=G.length)&&G.map((g,i)=>c(G[i],G[i?i-1:h-1],G[(i+1)%h]))&&L;l='';L((g,p,n)=>g.f=p.s(g).c(n.s(g))>0)((g,a,n)=>{d=g.s(n),y=x=1/d.l;g.f!=n.f?(a=asin((g.r+n.r)*x),g.f?(x=-x,a=-a):(y=-y)):(a=asin((g.r-n.r)*x),g.f&&(x=y=-x,a=-a));t=d.t(a+PI/2);g.o=t.m(x*g.r).a(g);n.i=t.m(y*n.r).a(n)})((g,p,n)=>{z='#888';d=(l,s,e)=>`A${g.r},${g.r} 0 ${1*l},${1*s} ${e}`;e=(f,r)=>T('circle',{cx:g.x,cy:g.y,r,fill:f});g.k=p.o.s(n.i).l<g.i.s(g.o).l;w=d(g.k,!g.f,g.o);g.j=`${w}L${n.i}`;l+=g.j;I(e(z,g.r-1.5));g.g=I(P({d:`M${g.i}${w}${d(!g.k,!g.f,g.i)}`,stroke:z,'stroke-width':5}));g.h=I(C(g.g,{d:`M${g.i}${g.j}`,stroke:'#222'}));I(e('#666',g.r-4.5));I(e(z,3))});t=e=>e.getTotalLength(),u='stroke-dasharray',v='stroke-dashoffset',f=G[0];l=I(C(f.h,{d:'M'+f.i+l,'stroke-width':2}));s=f.w=t(l)/round(t(l)/(4*PI))/2;X=8*s;Y=f.v=0;L((g,p)=>{g.g[b](u,s);g.h[b](u,s);g==f||(g.w=p.w+t(p.h),g.v=p.v+t(p.h));g.g[b](v,g.w);g.h[b](v,g.v);g.h[a](C(g.g[a](T('animate',{attributeName:v,from:g.w+X,to:g.w+Y,repeatCount:'indefinite',dur:'1s'})),{from:g.v+X,to:g.v+Y}))})}}

위의 함수는 SVG 요소 (애니메이션 포함)를 문서에 추가합니다. 예를 들어 두 번째 테스트 사례를 표시하려면

R([[100, 100, 60],  [220, 100, 14]]);

적어도 Chrome에서는 간식을 사용하는 것 같습니다.

아래 스 니펫에서 시도하십시오 (버튼을 클릭하면 각 OP의 테스트 사례가 그려집니다).

R=g=>{with(Math){V=(x,y,o)=>o={x,y,l:sqrt(x*x+y*y),a:v=>V(x+v.x,y+v.y),s:v=>o.a(v.m(-1)),m:f=>V(x*f,y*f),t:r=>V(x*cos(r)-y*sin(r),x*sin(r)+y*cos(r)),c:v=>x*v.y-y*v.x,toString:_=>x+','+y};a='appendChild',b='setAttribute';S=(e,a)=>Object.keys(a).map(n=>e[b](n,a[n]))&&e;T=(t,a)=>S(k.createElementNS('http://www.w3.org/2000/svg',t),a);C=(e,a)=>S(e.cloneNode(),a);P=a=>T('path',(a.fill='none',a));w=h=-(x=y=1/0);G=g.map((a,g)=>(g=V(...a))&&(u=(g.r=a[2])+5,x=min(x,g.x-u),y=min(y,g.y-u),w=max(w,g.x+u),h=max(h,g.y+u))&&g);k=document;I=k[a].bind(k.body[a](T('svg',{width:w-x,height:h-y}))[a](T('g',{transform:`translate(${-x},${h})scale(1,-1)`})));L=(c)=>(h=G.length)&&G.map((g,i)=>c(G[i],G[i?i-1:h-1],G[(i+1)%h]))&&L;l='';L((g,p,n)=>g.f=p.s(g).c(n.s(g))>0)((g,a,n)=>{d=g.s(n),y=x=1/d.l;g.f!=n.f?(a=asin((g.r+n.r)*x),g.f?(x=-x,a=-a):(y=-y)):(a=asin((g.r-n.r)*x),g.f&&(x=y=-x,a=-a));t=d.t(a+PI/2);g.o=t.m(x*g.r).a(g);n.i=t.m(y*n.r).a(n)})((g,p,n)=>{z='#888';d=(l,s,e)=>`A${g.r},${g.r} 0 ${1*l},${1*s} ${e}`;e=(f,r)=>T('circle',{cx:g.x,cy:g.y,r,fill:f});g.k=p.o.s(n.i).l<g.i.s(g.o).l;w=d(g.k,!g.f,g.o);g.j=`${w}L${n.i}`;l+=g.j;I(e(z,g.r-1.5));g.g=I(P({d:`M${g.i}${w}${d(!g.k,!g.f,g.i)}`,stroke:z,'stroke-width':5}));g.h=I(C(g.g,{d:`M${g.i}${g.j}`,stroke:'#222'}));I(e('#666',g.r-4.5));I(e(z,3))});t=e=>e.getTotalLength(),u='stroke-dasharray',v='stroke-dashoffset',f=G[0];l=I(C(f.h,{d:'M'+f.i+l,'stroke-width':2}));s=f.w=t(l)/round(t(l)/(4*PI))/2;X=8*s;Y=f.v=0;L((g,p)=>{g.g[b](u,s);g.h[b](u,s);g==f||(g.w=p.w+t(p.h),g.v=p.v+t(p.h));g.g[b](v,g.w);g.h[b](v,g.v);g.h[a](C(g.g[a](T('animate',{attributeName:v,from:g.w+X,to:g.w+Y,repeatCount:'indefinite',dur:'1s'})),{from:g.v+X,to:g.v+Y}))})}}


// ========================
// Test code

var testCases = [
  [[0, 0, 16],  [100, 0, 16],  [100, 100, 12],  [50, 50, 24],  [0, 100, 12]],
  [[0, 0, 26],  [120, 0, 26]],
  [[100, 100, 60],  [220, 100, 14]],
  [[100, 100, 16],  [100, 0, 24],  [0, 100, 24],  [0, 0, 16]],
  [[0, 0, 60],  [44, 140, 16],  [-204, 140, 16],  [-160, 0, 60],  [-112, 188, 12], [-190, 300, 30],  [30, 300, 30],  [-48, 188, 12]],
  [[0, 128, 14],  [46.17, 63.55, 10],  [121.74, 39.55, 14],  [74.71, -24.28, 10], [75.24, -103.55, 14],  [0, -78.56, 10],  [-75.24, -103.55, 14],  [-74.71, -24.28, 10], [-121.74, 39.55, 14],  [-46.17, 63.55, 10]],
  [[367, 151, 12],  [210, 75, 36],  [57, 286, 38],  [14, 181, 32],  [91, 124, 18], [298, 366, 38],  [141, 3, 52],  [80, 179, 26],  [313, 32, 26],  [146, 280, 10], [126, 253, 8],  [220, 184, 24],  [135, 332, 8],  [365, 296, 50],  [248, 217, 8], [218, 392, 30]]
];

function clear() {
  var buttons = document.createElement('div');
  document.body.innerHTML = "";
  document.body.appendChild(buttons);
  testCases.forEach(function (data, i) {
    var button = document.createElement('button');
    button.innerHTML = String(i);
    button.onclick = function () {
      clear();
      R(data);
      return false;
    };
    buttons.appendChild(button);
  });
}

clear();

이 코드는 체인과 기어 톱니를 점선으로 그립니다. 그런 다음 animate요소를 사용하여 stroke-dashoffset속성 에 애니메이션을 적용 합니다. 결과 SVG 요소는 독립적입니다. JS 중심의 애니메이션이나 CSS 스타일링은 없습니다.

사물을 깔끔하게 정렬하기 위해 각 기어의 톱니 고리는 실제로 두 개의 호로 구성된 경로로 그려 지므로 체인이 닿는 접선 지점에서 경로가 바로 시작될 수 있습니다. 이렇게하면 정렬하기가 훨씬 간단 해집니다.

또한 SVG의 대시 선을 사용할 때 많은 반올림 오류가있는 것으로 보입니다. 적어도 내가 본 것입니다. 체인이 길수록 각 기어와의 메시가 나빠집니다. 따라서 문제를 최소화하기 위해 체인은 실제로 여러 경로로 구성됩니다. 각 경로는 한 기어 주변의 원호 세그먼트와 다음 기어의 직선으로 구성됩니다. 대시 오프셋은 일치하도록 계산됩니다. 그러나 체인의 얇은 “내부”부분은 애니메이션되지 않기 때문에 단일 루프 경로 일뿐입니다.


답변

C # 3566 바이트

전혀 골프는 아니지만 작동합니다 (생각합니다)

편집 기록에서 풀리지 않습니다.

Magick.NET을 사용하여 gif를 렌더링합니다.

class S{public float x,y,r;public bool c;public double i,o,a=0,l=0;public S(float X,float Y,float R){x=X;y=Y;r=R;}}class P{List<S>q=new List<S>();float x=float.MaxValue,X=float.MinValue,y=float.MaxValue,Y=float.MinValue,z=0,Z=0,N;int w=0,h=0;Color c=Color.FromArgb(32,32,32);Pen p,o;Brush b,n,m;List<PointF>C;double l;void F(float[][]s){p=new Pen(c,2);o=new Pen(c,5);b=new SolidBrush(c);n=new SolidBrush(Color.FromArgb(134,132,129));m=new SolidBrush(Color.FromArgb(100,99,97));for(int i=0;i<s.Length;i++){float[]S=s[i];q.Add(new S(S[0],S[1],S[2]));if(S[0]-S[2]<x)x=S[0]-S[2];if(S[1]-S[2]<y)y=S[1]-S[2];if(S[0]+S[2]>X)X=S[0]+S[2];if(S[1]+S[2]>Y)Y=S[1]+S[2];}q[0].c=true;z=-x+16;Z=-y+16;w=(int)(X-x+32);h=(int)(Y-y+32);for(int i=0;i<=q.Count;i++)H(q[i%q.Count],q[(i+1)%q.Count],q[(i+2)%q.Count]);C=new List<PointF>();for(int i=0;i<q.Count;i++){S g=q[i],k=q[(i+1)%q.Count];if(g.c)for(double a=g.i;a<g.i+D(g.o,g.i);a+=Math.PI/(2*g.r)){C.Add(new PointF((float)(g.x+z+g.r*Math.Cos(a)),(float)(g.y+Z+g.r*Math.Sin(a))));}else
for(double a=g.o+D(g.i,g.o);a>g.o;a-=Math.PI/(2*g.r)){C.Add(new PointF((float)(g.x+z+g.r*Math.Cos(a)),(float)(g.y+Z+g.r*Math.Sin(a))));}C.Add(new PointF((float)(g.x+z+g.r*Math.Cos(g.o)),(float)(g.y+Z+g.r*Math.Sin(g.o))));C.Add(new PointF((float)(k.x+z+k.r*Math.Cos(k.i)),(float)(k.y+Z+k.r*Math.Sin(k.i))));k.l=E(C);}l=E(C);N=(float)(K(l)/10.0);o.DashPattern=new float[]{N,N};double u=q[0].i;for(int i=0;i<q.Count;i++){S g=q[i];double L=g.l/(N*5);g.a=g.i+((1-(L%2))/g.r*Math.PI*2)*(g.c?1:-1);}List<MagickImage>I=new List<MagickImage>();for(int i=0;i<t;i++){using(Bitmap B=new Bitmap(w,h)){using(Graphics g=Graphics.FromImage(B)){g.Clear(Color.White);g.SmoothingMode=System.Drawing.Drawing2D.SmoothingMode.AntiAlias;foreach(S U in q){float R=U.x+z,L=U.y+Z,d=7+2*U.r;PointF[]f=new PointF[4];for(double a=(i*(4.0/t));a<2*U.r;a+=4){double v=U.a+((U.c?-a:a)/U.r*Math.PI),j=Math.PI/U.r*(U.c?1:-1),V=v+j,W=V+j,r=U.r+3.5;f[0]=new PointF(R,L);f[1]=new PointF(R+(float)(r*Math.Cos(v)),L+(float)(r*Math.Sin(v)));f[2]=new PointF(R+(float)(r*Math.Cos(V)),L+(float)(r*Math.Sin(V)));f[3]=new PointF(R+(float)(r*Math.Cos(W)),L+(float)(r*Math.Sin(W)));g.FillPolygon(n,f);}d=2*(U.r-1.5f);g.FillEllipse(n,R-d/2,L-d/2,d,d);d=2*(U.r-4.5f);g.FillEllipse(m,R-d/2,L-d/2,d,d);d=6;g.FillEllipse(n,R-d/2,L-d/2,d,d);}g.DrawLines(p,C.ToArray());o.DashOffset=(N*2.0f/t)*i;g.DrawLines(o,C.ToArray());B.RotateFlip(RotateFlipType.RotateNoneFlipY);B.Save(i+".png",ImageFormat.Png);I.Add(new MagickImage(B));}}}using(MagickImageCollection collection=new MagickImageCollection()){foreach(MagickImage i in I){i.AnimationDelay=5;collection.Add(i);}QuantizeSettings Q=new QuantizeSettings();Q.Colors=256;collection.Quantize(Q);collection.Optimize();collection.Write("1.gif");}}int t=5;double D(double a,double b){double P=Math.PI,r=a-b;while(r<0)r+=2*P;return r%(2*P);}double E(List<PointF> c){double u=0;for(int i=0;i<c.Count-1;i++){PointF s=c[i];PointF t=c[i+1];double x=s.X-t.X,y=s.Y-t.Y;u+=Math.Sqrt(x*x+y*y);}return u;}double K(double L){double P=4*Math.PI;int i=(int)(L/P);float a=(float)L/i,b=(float)L/(i+1);if(Math.Abs(P-a)<Math.Abs(P-b))return a;return b;}void H(S a,S b,S c){double A=0,r=0,d=b.x-a.x,e=b.y-a.y,f=Math.Atan2(e,d)+Math.PI/2,g=Math.Atan2(e,d)-Math.PI/2,h=Math.Atan2(-e,-d)-Math.PI/2,i=Math.Atan2(-e,-d)+Math.PI/2;double k=c.x-b.x,n=c.y-b.y,l=Math.Sqrt(d*d+e*e);A=D(Math.Atan2(n,k),Math.Atan2(-e,-d));bool x=A>Math.PI!=a.c;b.c=x!=a.c;if(a.r!=b.r)r=a.r+(x?b.r:-b.r);f-=Math.Asin(r/l);g+=Math.Asin(r/l);h+=Math.Asin(r/l);i-=Math.Asin(r/l);b.i=x==a.c?h:i;a.o=a.c?g:f;}}

클래스 P에는 함수 F가 있습니다. 예:

static void Main(string[]a){
P p=new P();
float[][]s=new float[][]{
new float[]{10,200,20},
new float[]{240,200,20},
new float[]{190,170,10},
new float[]{190,150,10},
new float[]{210,120,20},
new float[]{190,90,10},
new float[]{160,0,20},
new float[]{130,170,10},
new float[]{110,170,10},
new float[]{80,0,20},
new float[]{50,170,10}
};
p.F(s);}


답변

자바 스크립트 (ES6) 1626 바이트

이 솔루션은 @Flambino 솔루션의 리버스 엔지니어링의 결과입니다.

R=g=>{with(Math){v='stroke';j=v+'-dasharray';q=v+'-dashoffset';m='appendChild';n='getTotalLength';b='setAttribute';z='#888';k=document;V=(x,y,r,o)=>o={x,y,r,l:sqrt(x*x+y*y),a:v=>V(x+v.x,y+v.y),s:v=>o.a(v.m(-1)),m:f=>V(x*f,y*f),t:r=>V(x*cos(r)-y*sin(r),x*sin(r)+y*cos(r)),c:v=>x*v.y-y*v.x,toString:_=>x+','+y};S=(e,a)=>Object.keys(a).map(n=>e[b](n,a[n]))&&e;T=(t,a)=>S(k.createElementNS('http://www.w3.org/2000/svg',t),a);C=(e,a)=>S(e.cloneNode(),a);w=h=-(x=y=1/0);G=g.map((a,g)=>(g=V(...a))&&(u=(g.r=a[2])+5,x=min(x,g.x-u),y=min(y,g.y-u),w=max(w,g.x+u),h=max(h,g.y+u))&&g);f=G[0];w-=x;h-=y;s=T('svg',{width:w,height:h,viewBox:x+' '+y+' '+w+' '+h,transform:'scale(1,-1)'});c='';L=(c)=>(h=G.length)&&G.map((g,i)=>c(G[i],G[(h+i-1)%h],G[(i+1)%h]))&&L;L((g,p,n)=>g.w=(p.s(g).c(n.s(g))>0))((g,p,n)=>{d=g.s(n),y=x=1/d.l;g.w!=n.w?(p=asin((g.r+n.r)*x),g.w?(x=-x,p=-p):(y=-y)):(p=asin((g.r-n.r)*x),g.w&&(x=y=-x,p=-p));t=d.t(p+PI/2);g.o=t.m(x*g.r).a(g);n.i=t.m(y*n.r).a(n)})((g,p,n)=>{l=(p.o.s(n.i).l<g.i.s(g.o).l);d=(l,e)=>`A${g.r} ${g.r} 0 ${+l} ${+!g.w} ${e}`;a=d(l,g.o);e=(f,r)=>T('circle',{cx:g.x,cy:g.y,r,fill:f});c+=a+'L'+n.i;s[m](e(z,g.r-1.5));s[m](e('#666',g.r-4.5));s[m](e(z,3));g.p=s[m](C(g.e=s[m](T('path',{d:'M'+g.i+a+d(!l,g.i),fill:'none',[v]:z,[v+'-width']:5})),{d:'M'+g.i+a+'L'+n.i,[v]:'#222'}))});c=C(f.p,{d:'M'+f.i+c,[v+'-width']:2});g=c[n]();y=8*(x=g/round(g/(4*PI))/2);f.g=x;f.h=0;L((g,p)=>{g!=f&&(g.g=p.g+p.p[n](),g.h=p.h+p.p[n]());S(g.p,{[j]:x,[q]:g.h})[m](C(S(g.e,{[j]:x,[q]:g.g})[m](T('animate',{attributeName:[q],from:g.g+y,to:g.g,repeatCount:'indefinite',dur:'1s'})),{from:g.h+y,to:g.h}))});k.body[m](s)[m](c)}}

언 골프 버전 :

class Vector {

    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.length = Math.sqrt(x * x + y * y);
    }

    add(vector) {

        return new Vector(this.x + vector.x, this.y + vector.y);
    }

    subtract(vector) {

        return new Vector(this.x - vector.x, this.y - vector.y);
    }

    multiply(scalar) {

        return new Vector(this.x * scalar, this.y * scalar);
    }

    rotate(radians) {

        const cos = Math.cos(radians);
        const sin = Math.sin(radians);

        return new Vector(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
    }

    cross(vector) {

        return this.x * vector.y - this.y * vector.x;
    }

    toString() {

        return `${this.x},${this.y}`;
    }
}

class Gear {

    constructor(x, y, radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    getVector() {

        return new Vector(this.x, this.y);
    }
}

const setAttributes = (element, attributes) => {

    Object.keys(attributes).forEach((attribute) => {
        element.setAttribute(attribute, attributes[attribute]);
    });
};

const createElement = (tagName, attributes) => {

    const element = document.createElementNS('http://www.w3.org/2000/svg', tagName);

    setAttributes(element, attributes);

    return element;
};

const cloneElement = (element, attributes) => {

    const clone = element.cloneNode();

    setAttributes(clone, attributes);

    return clone;
};

const createPath = (attributes) => {

    return createElement('path', {
        ...attributes,
        fill: 'none'
    });
};

const createCircle = (cx, cy, r, fill) => {

    return createElement('circle', {
        cx,
        cy,
        r,
        fill
    });
};

const loopGears = (gears, callback) => {

    const length = gears.length;

    gears.forEach((gear, index) => {

        const prevGear = gears[(length + index - 1) % length];
        const nextGear = gears[(index + 1) % length];

        callback(gear, prevGear, nextGear);
    });
};

const arcDescription = (radius, largeArcFlag, sweepFlag, endVector) => {

    return `A${radius} ${radius} 0 ${+largeArcFlag} ${+sweepFlag} ${endVector}`;
};

const renderGears = (data) => {

    let x = Infinity;
    let y = Infinity;
    let w = -Infinity;
    let h = -Infinity;

    const gears = data.map((params) => {

        const gear = new Gear(...params);
        const unit = params[2] + 5;

        x = Math.min(x, gear.x - unit);
        y = Math.min(y, gear.y - unit);
        w = Math.max(w, gear.x + unit);
        h = Math.max(h, gear.y + unit);

        return gear;
    });

    const firstGear = gears[0];

    w -= x;
    h -= y;

    const svg = createElement('svg', {
        width: w,
        height: h,
        viewBox: `${x} ${y} ${w} ${h}`,
        transform: `scale(1,-1)`
    });

    let chainPath = '';

    loopGears(gears, (gear, prevGear, nextGear) => {

        const gearVector = gear.getVector();
        const prevGearVector = prevGear.getVector().subtract(gearVector);
        const nextGearVector = nextGear.getVector().subtract(gearVector);

        gear.sweep = (prevGearVector.cross(nextGearVector) > 0);
    });

    loopGears(gears, (gear, prevGear, nextGear) => {

        const diffVector = gear.getVector().subtract(nextGear.getVector());

        let angle = 0;
        let x = 1 / diffVector.length;
        let y = x;

        if (gear.sweep === nextGear.sweep) {

            angle = Math.asin((gear.radius - nextGear.radius) * x);

            if (gear.sweep) {
                x = -x;
                y = -y;
                angle = -angle;
            }
        } else {

            angle = Math.asin((gear.radius + nextGear.radius) * x);

            if (gear.sweep) {
                x = -x;
                angle = -angle;
            } else {
                y = -y;
            }
        }

        const perpendicularVector = diffVector.rotate(angle + Math.PI / 2);

        gear.out = perpendicularVector.multiply(x * gear.radius).add(gear.getVector());
        nextGear.in = perpendicularVector.multiply(y * nextGear.radius).add(nextGear.getVector());
    });

    loopGears(gears, (gear, prevGear, nextGear) => {

        const largeArcFlag = (prevGear.out.subtract(nextGear.in).length < gear.in.subtract(gear.out).length);
        const arcPath = arcDescription(gear.radius, largeArcFlag, !gear.sweep, gear.out);

        const gearExterior = createCircle(gear.x, gear.y, gear.radius - 1.5, '#888');
        const gearInterior = createCircle(gear.x, gear.y, gear.radius - 4.5, '#666');
        const gearCenter = createCircle(gear.x, gear.y, 3, '#888');

        const gearTeeth = createPath({
            d: `M${gear.in}${arcPath}${arcDescription(gear.radius, !largeArcFlag, !gear.sweep, gear.in)}`,
            stroke: '#888',
            'stroke-width': 5
        });

        const chainParts = cloneElement(gearTeeth, {
            d: `M${gear.in}${arcPath}L${nextGear.in}`,
            stroke: '#222'
        });

        gear.teeth = gearTeeth;
        gear.chainParts = chainParts;

        chainPath += `${arcPath}L${nextGear.in}`;

        svg.appendChild(gearExterior);
        svg.appendChild(gearInterior);
        svg.appendChild(gearCenter);
        svg.appendChild(gearTeeth);
        svg.appendChild(chainParts);
    });

    const chain = cloneElement(firstGear.chainParts, {
        d: 'M' + firstGear.in + chainPath,
        'stroke-width': 2
    });

    const chainLength = chain.getTotalLength();
    const chainUnit = chainLength / Math.round(chainLength / (4 * Math.PI)) / 2;
    const animationOffset = 8 * chainUnit;

    loopGears(gears, (gear, prevGear) => {

        if (gear === firstGear) {
            gear.teethOffset = chainUnit;
            gear.chainOffset = 0;
        } else {
            gear.teethOffset = prevGear.teethOffset + prevGear.chainParts.getTotalLength();
            gear.chainOffset = prevGear.chainOffset + prevGear.chainParts.getTotalLength();
        }

        setAttributes(gear.teeth, {
            'stroke-dasharray': chainUnit,
            'stroke-dashoffset': gear.teethOffset
        });

        setAttributes(gear.chainParts, {
            'stroke-dasharray': chainUnit,
            'stroke-dashoffset': gear.chainOffset
        });

        const animate = createElement('animate', {
            attributeName: 'stroke-dashoffset',
            from: gear.teethOffset + animationOffset,
            to: gear.teethOffset,
            repeatCount: 'indefinite',
            dur: '1s'
        });

        const cloneAnimate = cloneElement(animate, {
            from: gear.chainOffset + animationOffset,
            to: gear.chainOffset
        });

        gear.teeth.appendChild(animate);
        gear.chainParts.appendChild(cloneAnimate);
    });

    svg.appendChild(chain);
    document.body.appendChild(svg);
};

var testCases = [
    [[0, 0, 16],  [100, 0, 16],  [100, 100, 12],  [50, 50, 24],  [0, 100, 12]],
    [[0, 0, 26],  [120, 0, 26]],
    [[100, 100, 60],  [220, 100, 14]],
    [[100, 100, 16],  [100, 0, 24],  [0, 100, 24],  [0, 0, 16]],
    [[0, 0, 60],  [44, 140, 16],  [-204, 140, 16],  [-160, 0, 60],  [-112, 188, 12], [-190, 300, 30],  [30, 300, 30],  [-48, 188, 12]],
    [[0, 128, 14],  [46.17, 63.55, 10],  [121.74, 39.55, 14],  [74.71, -24.28, 10], [75.24, -103.55, 14],  [0, -78.56, 10],  [-75.24, -103.55, 14],  [-74.71, -24.28, 10], [-121.74, 39.55, 14],  [-46.17, 63.55, 10]],
    [[367, 151, 12],  [210, 75, 36],  [57, 286, 38],  [14, 181, 32],  [91, 124, 18], [298, 366, 38],  [141, 3, 52],  [80, 179, 26],  [313, 32, 26],  [146, 280, 10], [126, 253, 8],  [220, 184, 24],  [135, 332, 8],  [365, 296, 50],  [248, 217, 8], [218, 392, 30]]
];

function clear() {
    var buttons = document.createElement('div');
    document.body.innerHTML = "";
    document.body.appendChild(buttons);
    testCases.forEach(function (data, i) {
        var button = document.createElement('button');
        button.innerHTML = String(i);
        button.onclick = function () {
            clear();
            renderGears(data);
            return false;
        };
        buttons.appendChild(button);
    });
}

clear();