Sprocket Science: animare un sistema di trasmissione a catena


97

L'obiettivo di questa sfida è quello di produrre un'animazione di un sistema di trasmissione a catena , composto da una serie di ruote dentate collegate tra loro da una catena .

Requisiti generali

Al programma verrà fornito un elenco di ruote dentate , specificate come (x, y, radius)terzine. Il sistema di trasmissione a catena risultante è composto da questi pignoni, collegati tra loro da una catena tesa chiusa che passa su ciascuno di essi, in ordine . Il tuo obiettivo è produrre un'animazione a ciclo continuo , che mostri il sistema in movimento. Ad esempio, dato l'input

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

, l'output dovrebbe essere simile

Esempio 1.

Il sistema di coordinate dovrebbe essere tale che l'asse x sia rivolto verso destra e l'asse y sia rivolto verso l'alto. Si può presumere che i raggi siano numeri pari o maggiori di o uguali a 8 (vedremo perché questo è importante in seguito.) Si può anche presumere che ci siano almeno due ruote dentate e che le ruote dentate non si intersecano tra loro. Le unitàdell'input non sono troppo critici. Tutti gli esempi e i casi di test in questo post usano i pixel come unità di input (quindi, ad esempio, il raggio della ruota dentata centrale nella figura precedente è di 24 pixel;) cerca di non deviare troppo da queste unità. Nel resto della sfida, le quantità spaziali si intendono date nelle stesse unità dell'input: assicurati di mantenere le proporzioni giuste! Le dimensioni dell'output dovrebbero essere leggermente più grandi del rettangolo di selezione di tutti i pignoni, abbastanza grandi da rendere visibile l'intero sistema. In particolare, le posizioni assolute dei pignoni non dovrebbero influire sull'uscita; dovrebbero solo le loro posizioni relative (quindi, per esempio, se spostassimo tutte le ruote dentate nell'esempio sopra della stessa quantità, l'output rimarrebbe lo stesso.)

La catena deve essere tangente ai pignoni su cui passa in tutti i punti di contatto e diritta ovunque. La catena dovrebbe passare sopra i pignoni in modo tale che i segmenti di catena adiacenti (ovvero parti della catena tra due pignoni, che si incontrano sullo stesso pignone) non si intersecano .

Intersezione a catena.

Ad esempio, mentre il sistema di sinistra sopra è valido, quello di mezzo non lo è, poiché i due segmenti di catena adiacenti che passano sopra il pignone in basso a sinistra si intersecano. Tuttavia, si noti che il sistema giusto è valido, poiché i due segmenti di catena che si intersecano non sono adiacenti (questo sistema è prodotto da un input diverso rispetto agli altri due).

Per semplificare le cose (r), puoi presumere che nessun pignone interseca lo scafo convesso dei suoi due pignoni vicini, o gli scafi convessi di ciascuno dei suoi vicini e dell'altro vicino. In altre parole, il pignone superiore nel diagramma seguente potrebbe non intersecare nessuna delle regioni ombreggiate.

Esclusione

I segmenti di catena possono intersecare i pignoni diversi da quelli su cui passano (come nell'ultimo caso di prova). In questo caso, la catena dovrebbe sempre apparire davanti ai pignoni.

Requisiti visivi

La catena dovrebbe consistere in una serie di maglie di larghezza alternata. La larghezza del collegamento stretto dovrebbe essere di circa 2 e la larghezza del collegamento largo dovrebbe essere di circa 5. La lunghezza di entrambi i tipi di collegamenti dovrebbe essere circa uguale. Il periododella catena, ovvero la lunghezza totale di una coppia larga / stretta di maglie, dovrebbe essere il numero più vicino a 4π che si adatta a un numero intero di volte nella lunghezza della catena. Ad esempio, se la lunghezza della catena è 1.000, allora il suo periodo dovrebbe essere 12,5, che è il numero più vicino a 4π (12,566 ...) che si adatta a un numero intero di volte (80) in 1.000. È importante che il periodo si adatti a un numero intero di volte nella lunghezza della catena, in modo che non vi siano artefatti nel punto in cui la catena si avvolge.

Catena


Una ruota dentata di raggio R dovrebbe essere composta da tre parti concentriche: un asse centrale , che dovrebbe essere un cerchio di raggio circa 3; il corpo del pignone , attorno all'asse, che dovrebbe essere un cerchio di raggio intorno a R - 4.5; e il bordo del pignone , attorno al corpo, che dovrebbe essere un cerchio di raggio intorno a
R - 1,5. Il bordo dovrebbe contenere anche i denti del pignone , che dovrebbe avere una larghezza di circa 4; le dimensioni e la spaziatura dei denti devono corrispondere alle dimensioni delle maglie della catena, in modo che si incastrino perfettamente.

rocchetto

Il periodo dei denti del pignone, cioè la distanza tra due denti consecutivi lungo la circonferenza del pignone, dovrebbe corrispondere al periodo della catena. Poiché il periodo è di circa 4π, ​​e poiché il raggio della ruota dentata è garantito pari, il periodo dovrebbe rientrare nella circonferenza della ruota dentata un numero quasi intero di volte, in modo che non vi siano artefatti evidenti nel punto in cui i denti del pignone si avvolgono.

È possibile utilizzare qualsiasi combinazione di colori per la catena, le diverse parti del pignone e lo sfondo, purché siano facilmente distinguibili . Lo sfondo potrebbe essere trasparente. Gli esempi in questo post usano Catena di colore #202020per la catena, Colore perno ruota e perno #868481per l'asse e il cerchione Colore corpo pignone #646361del pignone e per il corpo del pignone.

Requisiti di animazione

Il primo pignone nell'elenco di input dovrebbe ruotare in senso orario ; il resto dei pignoni dovrebbe ruotare di conseguenza. La catena dovrebbe muoversi ad una velocità di circa 16π (circa 50) unità al secondo; la frequenza dei fotogrammi dipende da te, ma l'animazione dovrebbe apparire abbastanza fluida.

L'animazione dovrebbe essere ripetuta senza interruzioni .

conformità

Alcuni degli attributi visivi e delle proporzioni sono intenzionalmente specificati solo approssimativamente, non è necessario abbinarli esattamente . L'output del tuo programma non deve essere una replica pixel per pixel degli esempi forniti qui, ma dovrebbe apparire simile. In particolare, le proporzioni esatte della catena e dei pignoni e la forma esatta delle maglie della catena e dei denti del pignone sono flessibili.

I punti più importanti da seguire sono questi:

  • La catena dovrebbe passare sui pignoni, nell'ordine di immissione, dalla direzione corretta.
  • La catena deve essere tangente ai pignoni in tutti i punti di contatto.
  • Le maglie della catena e i denti dei pignoni devono essere ben disposti, almeno fino a correggere la spaziatura e la fase.
  • La distanza tra le maglie della catena e i denti dei pignoni dovrebbe essere tale che non vi siano artefatti evidenti nel punto in cui si avvolgono.
  • I pignoni devono ruotare nella direzione corretta.
  • L'animazione dovrebbe essere ripetuta senza interruzioni.

Come nota finale, mentre, tecnicamente, l'obiettivo di questa sfida è quello di scrivere il codice più breve, se hai voglia di diventare creativo e produrre un output più elaborato, provaci!

Sfida

Scrivere un programma o una funzione , prendendo un elenco di ruote dentate e producendo un'animazione del sistema di trasmissione a catena corrispondente, come descritto sopra.

Ingresso e uscita

È possibile accettare l' input tramite la riga di comando , tramite STDIN , come argomenti di funzione o utilizzando un metodo equivalente . Puoi utilizzare qualsiasi formato utile per l'input, ma assicurati di specificarlo nel tuo post.

Come output , puoi visualizzare direttamente l'animazione , produrre un file di animazione (ad es. Una GIF animata) o produrre una sequenza di file di frame (tuttavia, in questo caso c'è una piccola penalità; vedi sotto). Se utilizzi l'output di file, assicurati che il numero di frame sia ragionevole (gli esempi in questo post usano pochissimi frame;) il numero di frame non deve essere minimo, ma non dovresti produrre troppi frame superflui. Se produci una sequenza di fotogrammi, assicurati di specificare la frequenza dei fotogrammi nel tuo post.

Punto

Questo è code-golf . La risposta più breve , in byte, vince.

+ 10% Penalità   Se il tuo programma produce una sequenza di fotogrammi come output, invece di visualizzare direttamente l'animazione o produrre un singolo file di animazione, aggiungi il 10% al tuo punteggio.

Casi test

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



Divertiti!


38
Queste gif sono molto soddisfacenti +1
Adnan,

24
Sarò colpito se qualcuno risponderà con successo con qualsiasi quantità di codice.
DavidC,

5
Come hai fatto le gif? E da quanto tempo funziona?
J Atkin,

10
@JAtkin Allo stesso modo tutti gli altri dovrebbero: ho scritto una soluzione :) Se mi stai chiedendo dei dettagli, ho usato Cairo per i singoli frame, quindi ho usato ImageMagick per creare le gif (BTW, se qualcuno vuole produrre l'animazione questo Ad esempio, generando prima i fotogrammi e quindi utilizzando uno strumento esterno per trasformarli in animazioni, sto benissimo , purché tu specifichi la dipendenza dallo strumento nel tuo post. Solo per essere chiari, è tuo programma che dovrebbe invocare lo strumento, non l'utente.)
Ell

5
@Anko La buona notizia è che non devi preoccuparti: questa situazione è garantita per non verificarsi nell'input; vedere la parte "nessun pignone interseca lo scafo convesso ...", quella con l'immagine con le tre regioni ombreggiate. Più in generale, la catena attraversa ogni pignone una sola volta, secondo l'ordine dei pignoni, anche se sembra che passi vicino a un pignone più di una volta.
Ell

Risposte:


42

JavaScript (ES6), 2557 1915 1897 1681 byte

Questo non è super-super giocato a golf davvero; è minimizzato - in parte a mano - ma non è niente di speciale. Senza dubbio potrebbe essere più breve se avessi giocato a golf di più prima di minimizzare, ma ho già trascorso (più che) abbastanza tempo su questo.

Modifica: Ok, quindi ho trascorso più tempo su di esso e ho giocato a golf più codice prima di minimizzare (molto manualmente questa volta). Il codice sta ancora usando lo stesso approccio e la stessa struttura generale, ma anche così ho finito per salvare 642 byte. Non troppo malandato, se lo dico io stesso. Probabilmente ho perso alcune opportunità di risparmio di byte, ma a questo punto non sono nemmeno sicuro di come funzioni. L'unica cosa che differisce in termini di output, è che ora utilizza colori leggermente diversi che potrebbero essere scritti più tersamente.

Modifica 2 (molto più tardi): 18 byte salvati. Grazie a ConorO'Brien nei commenti per aver sottolineato l'accecantemente ovvio che mi ero perso del tutto.

Modifica 3: Quindi, ho pensato di decodificare il mio codice, perché, sinceramente, non riuscivo a ricordare come l'avevo fatto e ho perso le versioni non giocate. Così ho attraversato, ed ecco, ho trovato altri 316 byte da salvare ristrutturando e facendo del micro golf.

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

La funzione sopra aggiunge un elemento SVG (comprese le animazioni) al documento. Ad esempio per visualizzare il secondo caso di test:

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

Sembra funzionare a meraviglia - almeno qui in Chrome.

Provalo nello snippet di seguito (facendo clic sui pulsanti si disegna ciascuno dei casi di test di OP).

Il codice disegna la catena e i denti degli ingranaggi come tratti tratteggiati. Quindi utilizza gli animateelementi per animare l' stroke-dashoffsetattributo. L'elemento SVG risultante è autonomo; non c'è animazione basata su JS o stile CSS.

Per allineare bene le cose, l'anello dentale di ogni ingranaggio è in realtà disegnato come un percorso costituito da due archi, quindi il percorso può iniziare proprio nel punto tangente in cui la catena tocca. Questo rende molto più semplice allinearlo.

Inoltre, sembra che ci siano molti errori di arrotondamento quando si usano i tratti tratteggiati di SVG. Almeno, è quello che ho visto; più lunga è la catena, peggio che si ingranerebbe con ogni marcia successiva. Quindi, per ridurre al minimo il problema, la catena è in realtà composta da diversi percorsi. Ogni percorso è costituito da un segmento arcuato attorno a una marcia e una linea retta alla marcia successiva. I loro trattini sono calcolati per corrispondere. La sottile parte "interna" della catena, tuttavia, è solo un singolo percorso ad anello, poiché non è animato.


2
Sembra fantastico! Complimenti per aver risposto a una vecchia sfida (ish)!
Ell

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

1
@Flambino, mi piace la tua soluzione per questa sfida e mi dispiace davvero che tu abbia perso la fonte originale, ho fatto un po 'di reverse enginnering per recuperarla, può essere trovata qui: gist.github.com/micnic/6aec085d63320229a778c6775ec7f9aa anche l'ho minimizzata manualmente a 1665 byte (può essere minimizzato di più, ma oggi sono pigro)
micnic

1
@micnic Grazie! Dovrò verificarlo! E non preoccuparti, sono riuscito a decodificare anche io, quindi ho una versione più leggibile. Ma, dang, 16 byte in meno? Complimenti! Lo darò sicuramente un'occhiata quando
riuscirò a

1
@Flambino, essenzialmente l'impatto maggiore sulla dimensione del file è stata la struttura svg, non ho inserito l'evething in a <g>, ma l'ho inserito direttamente nella radice svg. Hai anche trovato un posto dove hai trasformato la bandiera sweep e la bandiera ad arco grande da booleana a numero usando 1*x, ma potresti usare+x
micnic

40

C # 3566 byte

Non giocare a golf, ma funziona (penso)

Non registrato nella cronologia delle modifiche.

Usa Magick.NET per il rendering di 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;}}

La classe P ha una funzione F; Esempio:

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

inserisci qui la descrizione dell'immagine


2
Grazie per aver pubblicato una versione giocata a golf! Un piccolo cavillo: il primo pignone nella tua gif ruota in senso antiorario; il primo pignone dovrebbe sempre ruotare in senso orario.
Ell

Ho visto C # solo di passaggio, ma hai bisogno del publicmodificatore prima di ogni campo della tua classe?
J Atkin,

1
@JAtkin davvero, quelli sono tutti inutili per quanto posso dire. In altre questioni, PointF è davvero System.Drawing.PointF (simile per Elenco, Colore e Matematica), quindi usingdovrebbero essere incluse le clausole corrispondenti , o i tipi pienamente qualificati quando usati, e il riferimento a System.Drawing dovrebbe essere notato nella risposta (se debba aggiungere al punteggio non lo so). Risposta impressionante comunque.
VisualMelon

@JAtkin Ho due classi, S e P, quindi i campi in S sono tutti pubblici. Non sono sicuro che siano strettamente necessari, ma penso di sì ...
TFeld

3

JavaScript (ES6) 1626 byte

Questa soluzione è il risultato del reverse engineering della soluzione di @ Flambino, la inserisco con il suo accordo.

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

La versione ungolfed:

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
Puoi salvare più di 250 byte usando questo strumento.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.