Sprocket Science: Animating a Chain Drive System


96

Celem tego wyzwania jest stworzenie animacji układu napędu łańcuchowego , składającego się z zestawu kół zębatych połączonych ze sobą łańcuchem .

Ogólne wymagania

Twój program otrzyma listę kół zębatych , określonych jako (x, y, radius)trojaczki. Powstały układ napędowy łańcuch składa się z tych kół zębatych, połączonych ze sobą za pomocą zamkniętego łańcucha napiętego przechodzącej przez każdego z nich, w celu . Twoim celem jest stworzenie nieskończenie zapętlonej animacji pokazującej system w ruchu. Na przykład biorąc pod uwagę dane wejściowe

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

, wynik powinien wyglądać mniej więcej tak

Przykład 1.

Układ współrzędnych powinien być taki, aby oś x była skierowana w prawo, a oś y była skierowana w górę. Możesz założyć, że promienie są liczbami parzystymi większymi lub równymi 8 (zobaczymy, dlaczego to ma znaczenie później). Możesz również założyć, że są co najmniej dwa koła łańcuchowe i że koła zębate się nie przecinają . te jednostkiwejścia nie są zbyt krytyczne. Wszystkie przykłady i przypadki testowe w tym poście wykorzystują piksele jako jednostki wejściowe (więc na przykład promień środkowej zębatki na poprzedniej ilustracji wynosi 24 piksele;) staraj się nie odchylać zbytnio od tych jednostek. W pozostałej części wyzwania wielkości przestrzenne są rozumiane jako podane w tych samych jednostkach co dane wejściowe - pamiętaj, aby zachować odpowiednie proporcje! Te wymiary wyjścia powinna być nieco większa niż obwiedni wszystkich kół zębatych, wystarczająco duży, tak że cały system jest widoczny. W szczególności bezwzględne położenia kół łańcuchowych nie powinny wpływać na wydajność; tylko ich względne pozycje powinny (więc na przykład, gdybyśmy przesunęli wszystkie koła łańcuchowe w powyższym przykładzie o tę samą wartość, wynik pozostanie taki sam.)

Łańcuch powinien być styczny do kół łańcuchowych, przez które przechodzi, we wszystkich punktach styku i prosto wszędzie. Łańcuch powinien przechodzić nad kołami łańcuchowymi, tak aby sąsiednie segmenty łańcucha (to znaczy części łańcucha między dwoma kołami łańcuchowymi, które spotykają się na tej samej zębatce) nie przecinały się .

Przecięcie łańcucha.

Na przykład, podczas gdy lewy system powyżej jest prawidłowy, środkowy nie jest, ponieważ dwa sąsiednie segmenty łańcucha, które przechodzą nad dolną lewą zębatką, przecinają się. Należy jednak pamiętać, że właściwy system jest prawidłowy, ponieważ dwa przecinające się segmenty łańcucha nie sąsiadują ze sobą (ten system jest wytwarzany przez inny wkład niż dwa pozostałe).

Dla uproszczenia (r) możesz założyć, że żadne koło łańcuchowe nie przecina wypukłego kadłuba dwóch sąsiednich kół lub wypukłych kadłubów każdego z sąsiadów i drugiego sąsiada. Innymi słowy, górna zębatka na poniższym schemacie nie może przecinać żadnego z zacienionych obszarów.

Wykluczenie

Segmenty łańcucha mogą przecinać koła łańcuchowe inne niż te, które mijają (na przykład w ostatnim przypadku testowym). W takim przypadku łańcuch powinien zawsze pojawiać się przed zębami.

Wymagania wizualne

Łańcuch powinien składać się z szeregu ogniw o przemiennych szerokościach. Szerokość wąskiego łącza powinna wynosić około 2, a szerokość szerokiego łącza powinna wynosić około 5. Długość obu typów łączy powinna być w przybliżeniu równa. okresłańcucha, to znaczy całkowita długość szerokiej / wąskiej pary ogniw, powinna być liczbą najbliższą 4π, która odpowiada liczbie całkowitej na długości łańcucha. Na przykład, jeśli długość łańcucha wynosi 1000, wówczas jego okres powinien wynosić 12,5, co jest liczbą najbliższą 4π (12.566 ...), która pasuje do liczby całkowitej (80) na 1000. Ważne jest, aby kropka pasowała do liczby całkowitej liczbę razy, tak aby nie było żadnych artefaktów w miejscu, w którym łańcuch się owija.

Łańcuch


Koło łańcuchowe o promieniu R powinno składać się z trzech koncentrycznych części: środkowej osi , która powinna być okręgiem o promieniu około 3; w korpus koła łańcuchowego jest wokół osi, która powinna być w kole o promieniu R - 4,5; oraz obręcz koła łańcuchowego wokół ciała, która powinna być okręgiem o promieniu około
R - 1,5. Obręcz powinna również zawierać zęby koła łańcuchowego , które powinny mieć szerokość około 4; rozmiar i odstępy między zębami powinny odpowiadać rozmiarom ogniw łańcucha, aby były dobrze osadzone.

Koło zębate

Okres zębów koła łańcuchowego, to znaczy odległość między dwoma kolejnymi zębami wzdłuż obwodu koła łańcuchowego, powinna odpowiadać okresowi łańcucha. Ponieważ okres wynosi około 4π, a promień koła zębatego ma być równy, okres powinien pasować do obwodu koła zębatego prawie całkowitą liczbę razy, aby nie było żadnych zauważalnych artefaktów w punkcie, w którym zęby zębatki owijają się.

Możesz użyć dowolnej kombinacji kolorów dla łańcucha, różnych części zębatki i tła, o ile można je łatwo rozróżnić . Tło może być przezroczyste. Przykłady w tym poście wykorzystują Kolor łańcucha #202020łańcuch, Kolor koła zębatego i obręczy #868481oś i obręcz Kolor korpusu zębatki #646361koła łańcuchowego oraz korpus koła łańcuchowego.

Wymagania dotyczące animacji

Pierwsza zębatka na liście wejściowej powinno obracać się w prawo ; reszta kół zębatych powinna się odpowiednio obracać. Łańcuch powinien poruszać się z prędkością około 16π (około 50) jednostek na sekundę; szybkość klatek zależy od Ciebie, ale animacja powinna wyglądać wystarczająco płynnie.

Animacja powinna być płynnie zapętlona .

Zgodność

Niektóre wizualne atrybuty i proporcje są celowo określone tylko z grubsza - nie musisz ich dokładnie dopasowywać . Wyjście twojego programu nie musi być repliką pikseli z podanych tu przykładów, ale powinno wyglądać podobnie. W szczególności dokładne proporcje łańcucha i kół łańcuchowych oraz dokładny kształt ogniw łańcucha i zębów koła łańcuchowego są elastyczne.

Najważniejsze punkty do naśladowania to:

  • Łańcuch powinien przechodzić nad zębatkami, w kolejności wprowadzania, z właściwego kierunku.
  • Łańcuch powinien być styczny do kół łańcuchowych we wszystkich punktach styku.
  • Ogniwa łańcucha i zęby kół zębatych powinny dobrze zagłębiać się, przynajmniej do prawidłowego odstępu i fazy.
  • Odstęp między ogniwami łańcucha a zębami kół łańcuchowych powinien być taki, aby nie było zauważalnych artefaktów w miejscu, w którym się owijają.
  • Koła łańcuchowe powinny obracać się we właściwym kierunku.
  • Animacja powinna być płynnie zapętlona.

Na koniec, chociaż technicznie celem tego wyzwania jest napisanie najkrótszego kodu, jeśli masz ochotę być kreatywnym i uzyskać bardziej skomplikowane wyniki, zdecydowanie, idź!

Wyzwanie

Napisz program lub funkcję , pobierając listę kół zębatych i generując odpowiednią animację systemu napędu łańcuchowego, jak opisano powyżej.

Wejście i wyjście

Możesz pobrać dane wejściowe z wiersza poleceń , poprzez STDIN , jako argumenty funkcji lub przy użyciu równoważnej metody . Możesz użyć dowolnego dogodnego formatu danych wejściowych, ale pamiętaj, aby podać go w swoim poście.

Jako wynik możesz wyświetlić animację bezpośrednio , utworzyć plik animacji (np. Animowany GIF) lub sekwencję plików klatek (jednak w tym przypadku istnieje niewielka kara; patrz poniżej). Jeśli używasz pliku wyjściowego, upewnij się, że liczba ramek jest rozsądna (przykłady w tym poście używają bardzo niewielu ramek;) liczba ramek nie musi być minimalna, ale nie powinieneś tworzyć zbyt wielu zbędnych ramek. Jeśli wyprowadzasz sekwencję klatek, pamiętaj o określeniu liczby klatek w poście.

Wynik

To jest golf golfowy . Najkrótsza odpowiedź w bajtach, wygrywa.

+ 10% Kara   Jeśli twój program tworzy sekwencję klatek jako wyjście, zamiast bezpośrednio wyświetlać animację lub tworzyć pojedynczy plik animacji, dodaj 10% do swojego wyniku.

Przypadki testowe

Test 1

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

Test 1

Test 2

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

Test 2

Test 3

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

Test 3

Test 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)

Test 4

Test 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)

Test 5

Test 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)

Test 6



Baw się dobrze!


38
Te gify są bardzo satysfakcjonujące +1
Adnan

24
Będę pod wrażeniem, jeśli ktoś z powodzeniem odpowie na to przy pomocy dowolnej ilości kodu.
DavidC,

5
Jak stworzyłeś gify? A jak długo to trwa w pracach?
J Atkin

10
@JAtkin Tak samo wszyscy inni: napisałem rozwiązanie :) Jeśli pytasz o szczegóły, użyłem Kairu dla poszczególnych ramek, a następnie użyłem ImageMagick do stworzenia gifów (BTW, jeśli ktoś chce stworzyć animację w ten sposób sposób, czyli przez pierwsze generowania klatek, a następnie przy użyciu zewnętrznego narzędzia, aby włączyć je do animacji, jestem całkowicie w porządku z tym, jak długo można określić zależność od narzędzia w swoim poście. Żeby było jasne, to jest twoja program, który powinien wywoływać narzędzie, a nie użytkownika.)
Ell

5
@Anko Dobrą wiadomością jest to, że nie musisz się tym martwić: taka sytuacja z pewnością nie wystąpi w danych wejściowych; patrz część „żadne koło nie przecina wypukłego kadłuba ...”, ta z obrazem z trzema zacienionymi obszarami. Mówiąc bardziej ogólnie, łańcuch przecina każdą zębatkę tylko jeden raz, zgodnie z kolejnością zębatek, nawet jeśli wygląda na to, że więcej niż raz przechodzi koło zębatego.
Ell

Odpowiedzi:


42

JavaScript (ES6), 2557 1915 1897 1681 bajtów

To naprawdę nie jest gra w super- dupera ; jest zminimalizowany - częściowo ręcznie - ale to nic specjalnego. Bez wątpienia byłbym krótszy, gdybym grał bardziej w golfa przed minifikacją, ale spędziłem (więcej niż) wystarczająco dużo czasu na tym.

Edycja: Ok, więc spędziłem na nim więcej czasu i grałem w golfa więcej kodu, zanim go zminimalizowałem (tym razem bardzo ręcznie). Kod nadal korzysta z tego samego podejścia i ogólnej struktury, ale mimo to udało mi się zaoszczędzić 642 bajty. Niezbyt obskurny, jeśli sam to powiem. Prawdopodobnie przegapiłem kilka możliwości oszczędzania bajtów, ale w tym momencie nawet nie jestem pewien, jak to działa. Jedyną rzeczą, która różni się pod względem wyników, jest to, że używa teraz nieco innych kolorów, które można by bardziej precyzyjnie napisać.

Edycja 2 (znacznie później): Zapisano 18 bajtów. Dzięki ConorO'Brien w komentarzach za zwrócenie uwagi na oślepiająco oczywiste, że całkowicie za nim tęskniłem.

Edycja 3: Pomyślałem więc, że opracuję kod źródłowy, ponieważ szczerze mówiąc, nie pamiętałem, jak to zrobiłem, i straciłem niepoprawne wersje. Więc przeszedłem i oto znalazłem kolejne 316 bajtów do zaoszczędzenia poprzez restrukturyzację i trochę mikro golfa.

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

Powyższa funkcja dołącza do dokumentu element SVG (w tym animacje). Np. Aby wyświetlić 2. przypadek testowy:

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

Wydaje się działać - przynajmniej tutaj w Chrome.

Wypróbuj go we fragmencie poniżej (kliknięcie przycisków spowoduje narysowanie każdego z przypadków testowych OP).

Kod rysuje zęby łańcucha i koła zębatego jako pociągnięcia przerywane. Następnie używa animateelementów do animowania stroke-dashoffsetatrybutu. Wynikowy element SVG jest samowystarczalny; nie ma animacji opartej na JS ani stylów CSS.

Aby wszystko układało się dobrze, pierścień zębów każdego koła zębatego jest w rzeczywistości narysowany jako ścieżka składająca się z dwóch łuków, więc ścieżka może rozpocząć się dokładnie w punkcie stycznym, w którym styka się łańcuch. To sprawia, że ​​dużo łatwiej jest ustawić go w szeregu.

Co więcej, wydaje się, że istnieje wiele błędów zaokrąglania podczas używania pociągnięć kreskowanych SVG. Tak przynajmniej widziałem; im dłuższy łańcuch, tym gorzej będzie się zazębiał z każdym kolejnym biegiem. Aby zminimalizować problem, łańcuch składa się z kilku ścieżek. Każda ścieżka składa się z łukowego segmentu wokół jednego biegu i linii prostej do następnego biegu. Ich przesunięcia są obliczane tak, aby pasowały do ​​siebie. Cienka „wewnętrzna” część łańcucha stanowi jednak tylko jedną pętlę, ponieważ nie jest animowana.


2
Wygląda świetnie! Wyrazy uznania za odpowiedź na stare (ish) wyzwanie!
Ell

1
-2 bajty:R=g=>...
Conor O'Brien

1
@Flambino, podoba mi się twoje rozwiązanie dla tego wyzwania i bardzo mi przykro, że straciłeś oryginalne źródło, zrobiłem trochę inżynierii wstecznej, aby je odzyskać, można je znaleźć tutaj: gist.github.com/micnic/6aec085d63320229a778c6775ec7f9aa również zminimalizowałem to ręcznie do 1665 bajtów (można to zmniejszyć bardziej, ale dzisiaj jestem leniwy)
micnic

1
@micnic Thanks! Będę musiał to sprawdzić! I nie martw się, udało mi się też go odtworzyć, więc mam bardziej czytelną wersję. Ale, do cholery, 16 bajtów mniej? Sława! Na pewno dam mu spojrzenie, kiedy będę mógł znaleźć czas
Flambino,

1
@Flambino, zasadniczo największy wpływ na rozmiar pliku miała struktura svg, nie dodawałem nic do a <g>, ale umieszczałem je bezpośrednio w katalogu głównym svg. Znalazłem też miejsce, w którym przekształciłeś flagę zamiatania i flagę dużego łuku z wartości logicznej na 1*x+x
liczbową

40

C # 3566 bajtów

W ogóle nie grałem w golfa, ale działa (myślę)

Ungolfed w historii edycji.

Używa Magick.NET do renderowania 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;}}

Klasa P ma funkcję F; Przykład:

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

wprowadź opis zdjęcia tutaj


2
Dziękujemy za opublikowanie wersji golfowej! Drobna sprzeczka: pierwsza zębatka w twoim gifie obraca się w lewo; pierwsza zębatka powinna zawsze obracać się zgodnie z ruchem wskazówek zegara.
Ell

Widziałem tylko C # przy przejściu, ale czy potrzebujesz publicmodyfikatora przed każdym polem w swojej klasie?
J Atkin,

1
@JAtkin, o ile wiem, są one niepotrzebne. W innych kwestiach PointF to tak naprawdę System.Drawing.PointF (podobny do List, Color i Math), więc należy uwzględnić odpowiednie usingklauzule lub typy w pełni kwalifikowane, gdy są używane, i należy zauważyć odniesienie do System.Drawing w odpowiedzi (czy to powinno dodać do wyniku, którego nie znam). W każdym razie imponująca odpowiedź.
VisualMelon,

@JAtkin Mam dwie klasy, S i P, więc wszystkie pola w S są publiczne. Nie jestem pewien, czy są one absolutnie potrzebne, ale myślę, że tak ..
TFeld,

3

JavaScript (ES6) 1626 bajtów

To rozwiązanie jest wynikiem inżynierii odwrotnej rozwiązania @ Flambino, zamieszczam je z jego zgodą.

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

Wersja bez golfa:

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


1
Za pomocą tego narzędzia można zapisać ponad 250 bajtów .
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.