Sprocket Science: Animation eines Kettenantriebssystems

97

Ziel dieser Herausforderung ist es, eine Animation eines Kettenantriebssystems zu erstellen , das aus einem Satz Kettenrädern besteht, die durch eine Kette miteinander verbunden sind .

Allgemeine Anforderungen

Ihr Programm erhält eine Liste von Kettenrädern , die als (x, y, radius)Drillinge angegeben sind. Das sich ergebende Kettenantriebssystem ist dieser Kettenräder umfasst, von einem miteinander verbundenen geschlossenen gespannten Kette über jede von ihnen vorbei, um . Ihr Ziel ist es, eine Endlosschleifen-Animation zu erstellen , die das System in Bewegung zeigt. Zum Beispiel angesichts der Eingabe

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

sollte die Ausgabe ungefähr so ​​aussehen

Beispiel 1.

Das Koordinatensystem sollte so sein, dass die x-Achse nach rechts und die y-Achse nach oben zeigt. Sie können davon ausgehen, dass die Radien gerade Zahlen größer oder gleich 8 sind (wir werden später sehen, warum dies wichtig ist.) Sie können auch davon ausgehen, dass mindestens zwei Kettenräder vorhanden sind und dass sich die Kettenräder nicht schneiden . Die Einheitendes Eingangs sind nicht zu kritisch. In allen Beispielen und Testfällen in diesem Beitrag werden Pixel als Eingabeeinheiten verwendet (z. B. beträgt der Radius des mittleren Kettenrads in der vorherigen Abbildung 24 Pixel;). Versuchen Sie, nicht zu stark von diesen Einheiten abzuweichen. In der restlichen Herausforderung werden räumliche Größen in den gleichen Einheiten wie die Eingabe angegeben - achten Sie darauf, dass die Proportionen stimmen! Die Abmessungen des Abtriebs sollten geringfügig größer sein als der Begrenzungsrahmen aller Kettenräder und so groß sein, dass das gesamte System sichtbar ist. Insbesondere sollten die absoluten Positionen der Kettenräder die Leistung nicht beeinflussen; nur ihre relativen Positionen sollten (also, wenn wir zum Beispiel alle Kettenräder im obigen Beispiel um den gleichen Betrag verschoben, würde die Ausgabe gleich bleiben.)

Die Kette sollte an allen Berührungspunkten tangential zu den Kettenrädern und an allen anderen Stellen gerade sein . Die Kette sollte so über die Kettenräder geführt werden, dass sich benachbarte Kettensegmente (dh Teile der Kette zwischen zwei Kettenrädern, die sich am selben Kettenrad treffen) nicht schneiden .

Kettenschnittpunkt.

Während beispielsweise das linke System oben gültig ist, ist das mittlere nicht gültig, da sich die beiden benachbarten Kettensegmente, die über das untere linke Kettenrad verlaufen, kreuzen. Beachten Sie jedoch, daß das richtige System ist gültig, da die beiden sich schneidende Kettensegmente nicht benachbart sind (dieses System durch einen anderen Eingang als die beiden anderen erzeugt wird, wenn.)

Um die Dinge einfach zu halten (r), können Sie annehmen, dass kein Kettenrad die konvexe Hülle der beiden benachbarten Kettenräder oder die konvexen Hüllen der Nachbarn und der anderen Nachbarn schneidet . Mit anderen Worten, das obere Kettenrad in der folgenden Abbildung schneidet möglicherweise keinen der schattierten Bereiche.

Ausschluss

Kettensegmente können andere Kettenräder als die überfahrenen kreuzen (wie im letzten Testfall). In diesem Fall sollte die Kette immer vor den Kettenrädern erscheinen.

Visuelle Anforderungen

Die Kette sollte aus einer Reihe von Gliedern unterschiedlicher Breite bestehen. Die Breite des schmalen Glieds sollte ungefähr 2 betragen, und die Breite des breiten Glieds sollte ungefähr 5 betragen. Die Länge beider Gliedertypen sollte ungefähr gleich sein. Die Periodeder Kette, dh die Gesamtlänge eines breiten / schmalen Gliederpaares, sollte der 4π-Zahl am nächsten kommen, die eine ganzzahlige Anzahl von Malen in der Länge der Kette entspricht. Wenn die Länge der Kette beispielsweise 1.000 beträgt, sollte ihre Periode 12,5 betragen. Dies ist die Zahl, die 4π (12.566 ...) am nächsten kommt und eine ganzzahlige Anzahl von Malen (80) von 1.000 ergibt. Es ist wichtig, dass die Periode eine ganzzahlige Anzahl von Malen in die Kettenlänge passt, damit an der Stelle, an der sich die Kette wickelt, keine Artefakte vorhanden sind.

Kette


Ein Kettenrad mit Radius R sollte aus drei konzentrischen Teilen bestehen: eine zentrale Achse , die ein Kreis mit dem Radius sollte etwa 3 ist ; der Körper des Kettenrads um die Achse, der einen Radius von etwa R - 4,5 haben sollte; und die Felge des Kettenrads um den Körper, die einen Radius von etwa
R - 1,5 haben sollte. Die Felge sollte auch die Zähne des Kettenrads enthalten , die eine Breite von ungefähr 4 haben sollten; Die Größe und der Abstand der Zähne sollten mit den Größen der Kettenglieder übereinstimmen, damit sie gut ineinander greifen.

Kettenrad

Die Periode der Zähne des Kettenrads, dh der Abstand zwischen zwei aufeinanderfolgenden Zähnen entlang des Umfangs des Kettenrads, sollte mit der Periode der Kette übereinstimmen. Da die Periode ungefähr 4 & pgr; beträgt und der Radius des Kettenrads garantiert gleichmäßig ist, sollte die Periode fast ganzzahlig in den Umfang des Kettenrads passen, damit an der Stelle, an der keine Artefakte auftreten, keine erkennbaren Artefakte auftreten Die Zähne des Kettenrads winden sich.

Sie können eine beliebige Kombination von Farben für die Kette, die verschiedenen Teile des Kettenrads und den Hintergrund verwenden, sofern diese leicht zu unterscheiden sind . Der Hintergrund kann transparent sein. Die Beispiele in diesem Beitrag gelten Kettenfarbe #202020für die Kette, Kettenradachse und Felgenfarbe #868481für die Achse und die Felge Kettenrad Gehäusefarbe #646361des Kettenrads und für den Körper des Kettenrads.

Animationsanforderungen

Das erste Kettenrad in der Eingabeliste sollte sich im Uhrzeigersinn drehen . Die übrigen Kettenräder sollten sich entsprechend drehen. Die Kette sollte sich mit einer Geschwindigkeit von ungefähr 16π (ungefähr 50) Einheiten pro Sekunde bewegen . Die Framerate liegt bei Ihnen, aber die Animation sollte glatt genug aussehen.

Die Animation sollte sich nahtlos wiederholen .

Konformität

Einige der visuellen Attribute und Proportionen werden absichtlich nur grob angegeben - Sie müssen nicht genau mit ihnen übereinstimmen . Die Ausgabe Ihres Programms muss keine Pixel-zu-Pixel-Kopie der hier angegebenen Beispiele sein, sie sollte jedoch ähnlich aussehen. Insbesondere sind die genauen Proportionen der Kette und der Kettenräder sowie die genaue Form der Kettenglieder und der Kettenradzähne flexibel.

Die wichtigsten Punkte, die zu beachten sind, sind folgende:

  • Die Kette muss in der richtigen Reihenfolge über die Kettenräder geführt werden.
  • Die Kette sollte an allen Berührungspunkten tangential zu den Kettenrädern sein.
  • Die Glieder der Kette und die Zähne der Kettenräder sollten sauber ineinandergreifen, zumindest bis zum richtigen Abstand und der richtigen Phase.
  • Der Abstand zwischen den Gliedern der Kette und den Zähnen der Kettenräder sollte so sein, dass an der Stelle, an der sie sich wickeln, keine erkennbaren Artefakte auftreten.
  • Die Kettenräder sollten sich in die richtige Richtung drehen.
  • Die Animation sollte sich nahtlos wiederholen.

Letztendlich ist es das Ziel dieser Herausforderung, technisch gesehen den kürzesten Code zu schreiben. Wenn Sie jedoch kreativ werden und eine aufwändigere Ausgabe erzielen möchten, sollten Sie sich auf jeden Fall dafür entscheiden!

Herausforderung

Schreiben Sie ein Programm oder eine Funktion , erstellen Sie eine Liste der Kettenräder und erstellen Sie eine entsprechende Animation des Kettenantriebssystems, wie oben beschrieben.

Ein- und Ausgang

Sie können die Eingabe über die Befehlszeile , über STDIN , als Funktionsargumente oder mit einer äquivalenten Methode vornehmen . Sie können jedes geeignete Format für die Eingabe verwenden, müssen es jedoch in Ihrem Beitrag angeben.

Als Ausgabe können Sie die Animation direkt anzeigen , eine Animationsdatei (z. B. ein animiertes GIF) oder eine Folge von Frame-Dateien erstellen (in diesem Fall ist jedoch ein kleiner Nachteil zu erwarten; siehe unten). Wenn Sie die Dateiausgabe verwenden, Stellen Sie sicher, dass die Anzahl der Frames angemessen ist (in den Beispielen in diesem Beitrag werden nur sehr wenige Frames verwendet;). Die Anzahl der Frames muss nicht minimal sein, Sie sollten jedoch nicht zu viele überflüssige Frames erstellen. Wenn Sie eine Folge von Frames ausgeben , müssen Sie die Framerate in Ihrem Beitrag angeben .

Ergebnis

Das ist Code-Golf . Die kürzeste Antwort in Bytes gewinnt.

+ 10% Strafe   Wenn Ihr Programm eine Folge von Bildern als Ausgabe erstellt, anstatt die Animation direkt anzuzeigen oder eine einzelne Animationsdatei zu erstellen, erhöhen Sie Ihre Punktzahl um 10%.

Testfälle

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



Habe Spaß!

Ell
quelle
38
Diese Gifs sind sehr zufriedenstellend. +1
Adnan
24
Ich werde beeindruckt sein, wenn jemand dies erfolgreich mit einer beliebigen Menge Code beantwortet.
DavidC
5
Wie hast du die Gifs gemacht? Und wie lange ist das schon in Arbeit?
J Atkin
10
@JAtkin So sollten es auch alle anderen tun: Ich habe eine Lösung geschrieben :) Wenn Sie nach den Einzelheiten fragen, habe ich Cairo für die einzelnen Frames und dann ImageMagick zum Erstellen der Gifs verwendet (übrigens, wenn jemand diese Animation produzieren möchte) Art und Weise, das heißt , indem man zuerst die Rahmen zu erzeugen und dann ein externes Tool verwenden sie in Animation zu machen, ich bin völlig in Ordnung mit , dass, solange Sie die Abhängigkeit von dem Werkzeug in Ihrem Beitrag angeben. Nur um klar zu sein, es ist Ihre Programm , das das Tool aufrufen soll, nicht der Benutzer.)
Ell
5
@Anko Die gute Nachricht ist, dass Sie sich darüber keine Sorgen machen müssen: Diese Situation wird bei der Eingabe garantiert nicht eintreten. siehe den Teil "kein Kettenrad schneidet den konvexen Rumpf ...", der mit dem Bild mit den drei schattierten Bereichen. Im Allgemeinen kreuzt die Kette jedes Kettenrad nur einmal, entsprechend der Reihenfolge der Kettenräder, auch wenn es so aussieht, als würde sie mehr als einmal in der Nähe eines Kettenrads vorbeifahren.
Ell

Antworten:

42

JavaScript (ES6), 2557 1915 1897 1681 Byte

Das ist wirklich kein Super-Duper- Golf ; Es wird teilweise von Hand verkleinert, aber das ist nichts Besonderes. Es könnte zweifellos kürzer sein, wenn ich vor dem Minimieren mehr Golf gespielt hätte, aber ich habe (mehr als) genug Zeit darauf verwendet.

Bearbeiten: Ok, also habe ich mehr Zeit darauf verwendet und den Code mehr golfen, bevor ich ihn minimierte (diesmal sehr manuell). Der Code verwendet immer noch den gleichen Ansatz und die gleiche Gesamtstruktur, aber trotzdem habe ich 642 Bytes gespart. Nicht zu schäbig, wenn ich es selbst sage. Vermutlich habe ich einige Möglichkeiten zum Speichern von Bytes verpasst, aber zu diesem Zeitpunkt bin ich mir nicht sicher, wie das funktioniert. Das einzige, was sich in Bezug auf die Ausgabe unterscheidet, ist, dass jetzt leicht unterschiedliche Farben verwendet werden, die knapper geschrieben werden könnten.

Edit 2 (viel später): 18 Bytes gespeichert. Vielen Dank an ConorO'Brien in den Kommentaren für den Hinweis auf die verblüffend offensichtliche Tatsache, die ich völlig verpasst hatte.

Bearbeiten 3: Also dachte ich, ich würde meinen eigenen Code zurückentwickeln, weil ich mich ehrlich gesagt nicht erinnern konnte, wie ich es gemacht hatte, und ich verlor die ungolften Versionen. Also ging ich durch und siehe da, ich fand weitere 316 Bytes, die ich durch Umstrukturierung und ein bisschen Mikrogolf sparen konnte.

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

Mit der obigen Funktion wird ein SVG-Element (einschließlich Animationen) an das Dokument angehängt. ZB um den 2. Testfall anzuzeigen:

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

Scheint ein Vergnügen zu sein - zumindest hier in Chrome.

Probieren Sie es im folgenden Snippet aus (durch Klicken auf die Schaltflächen werden die einzelnen OP-Testfälle gezeichnet).

Der Code zeichnet die Ketten- und Zahnradzähne als gestrichelte Striche. Anschließend wird animatedas stroke-dashoffsetAttribut mithilfe von Elementen animiert . Das resultierende SVG-Element ist in sich geschlossen. Es gibt keine JS-gesteuerten Animationen oder CSS-Stile.

Damit sich die Dinge gut aneinanderreihen, wird der Zahnkranz jedes Zahnrads als Pfad gezeichnet, der aus zwei Bögen besteht, sodass der Pfad genau an dem Tangentenpunkt beginnen kann, an dem sich die Kette berührt. Dies macht es viel einfacher, es aufzustellen.

Darüber hinaus scheint es eine Menge Rundungsfehler zu geben, wenn die gestrichelten Striche von SVG verwendet werden. Zumindest habe ich das gesehen; Je länger die Kette, desto schlimmer kämmte sie mit jedem weiteren Gang. Um das Problem zu minimieren, besteht die Kette tatsächlich aus mehreren Pfaden. Jeder Pfad besteht aus einem Bogensegment um einen Gang und einer geraden Linie zum nächsten Gang. Ihre Strich-Offsets werden passend berechnet. Der dünne "innere" Teil der Kette ist jedoch nur ein einziger Schleifenpfad, da er nicht animiert ist.

Flambino
quelle
2
Sieht großartig aus! Ein großes Lob für die Beantwortung einer alten (ish) Herausforderung!
Ell
1
-2 Bytes:R=g=>...
Conor O'Brien
1
@Flambino, Ich mag Ihre Lösung für diese Herausforderung , und ich war wirklich traurig , dass Sie die Originalquelle verloren, machte ich einige Reverse enginnering es zu erholen, kann es hier zu finden: gist.github.com/micnic/6aec085d63320229a778c6775ec7f9aa auch minimierte ich es manuell auf 1665 Bytes (es kann mehr verkleinert werden, aber ich bin heute faul)
micnic
1
@micnic Danke! Ich muss das überprüfen! Und keine Sorge, ich habe es auch geschafft, es rückgängig zu machen, sodass ich eine besser lesbare Version habe. Aber, verdammt, 16 Bytes weniger? Ein dickes Lob! Ich werde auf jeden Fall einen Blick darauf werfen, wann ich die Zeit finde
Flambino
1
@Flambino, im Grunde genommen war der größte Einfluss auf die Dateigröße die SVG-Struktur. Ich habe nichts in ein gesetzt <g>, sondern es direkt in das SVG-Stammverzeichnis. Es wurde auch ein Ort gefunden, an dem Sie die Sweep-Flagge und die große Bogen-Flagge mit von Boolescher in Zahl verwandelt haben 1*x, aber Sie könnten+x
micnic 27.09.17
40

C # 3566 Bytes

Gar nicht golfen, aber funktioniert (glaube ich)

Ungolfed in der Editiergeschichte.

Verwendet Magick.NET zum Rendern von 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;}}

Klasse P hat eine Funktion F; Beispiel:

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

Bildbeschreibung hier eingeben

TFeld
quelle
2
Vielen Dank für die Veröffentlichung einer Golfversion! Ein kleines Problem: Das erste Kettenrad in Ihrem GIF dreht sich gegen den Uhrzeigersinn. Das erste Kettenrad sollte sich immer im Uhrzeigersinn drehen.
Ell
Ich habe C # nur im Vorbeigehen gesehen, aber benötigen Sie den publicModifikator vor jedem Feld in Ihrer Klasse?
J Atkin
1
@JAtkin in der Tat, das ist alles unnötig, soweit ich das beurteilen kann. In anderen Fällen ist PointF in Wirklichkeit System.Drawing.PointF (ähnlich für List, Color und Math), daher sollten die entsprechenden usingKlauseln enthalten sein oder die Typen, die bei Verwendung vollständig qualifiziert sind, und der Verweis auf System.Drawing sollte beachtet werden in der Antwort (ob es zur Kerbe hinzufügen sollte, weiß ich nicht). Beeindruckende Antwort jedenfalls.
VisualMelon
@JAtkin Ich habe zwei Klassen, S und P, also sind die Felder in S alle öffentlich. Ich bin mir nicht sicher, ob sie unbedingt benötigt werden, aber ich denke schon ..
TFeld
3

JavaScript (ES6) 1626 Byte

Diese Lösung ist das Ergebnis des Reverse Engineerings von @ Flambinos Lösung, ich poste es mit seinem Einverständnis.

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

Die ungolfed Version:

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

micnic
quelle
1
Mit diesem Tool können Sie mehr als 250 Byte speichern .