Sprocket Science: Animation d’un système d’entraînement par chaîne

97

L’objectif de ce défi est de produire une animation d’un système d’ entraînement par chaîne , composé d’un ensemble de pignons reliés entre eux par une chaîne .

Exigences générales

Votre programme recevra une liste de pignons , spécifiés en (x, y, radius)triplets. Le système d’entraînement par chaîne résultant est constitué de ces pignons, reliés entre eux par une chaîne tendue fermée passant au-dessus de chacun d’eux, dans l’ordre . Votre objectif est de produire une animation en boucle infinie , montrant le système en mouvement. Par exemple, étant donné l'entrée

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

, la sortie devrait ressembler à quelque chose comme

Exemple 1.

Le système de coordonnées doit être tel que l’axe des x pointe vers la droite et l’axe des y vers le haut. Vous pouvez supposer que les rayons sont des nombres pairs supérieurs ou égaux à 8 (nous verrons pourquoi cela compte plus tard.) Vous pouvez également supposer qu'il y a au moins deux pignons et que les pignons ne se croisent pas . Les unitésde l'entrée ne sont pas trop critiques. Tous les exemples et scénarios de test de cette publication utilisent des pixels comme unités d'entrée (ainsi, par exemple, le rayon du pignon central de la figure précédente est de 24 pixels;) essayez de ne pas trop vous écarter de ces unités. Dans la suite du défi, les quantités spatiales sont censées être exprimées dans les mêmes unités que l'entrée - veillez à conserver les proportions! Les dimensions de la sortie doivent être légèrement plus grandes que le cadre de sélection de tous les pignons, suffisamment grandes pour que tout le système soit visible. En particulier, les positions absolues des pignons ne devraient pas affecter la sortie; seules leurs positions relatives doivent (ainsi, par exemple, si nous décalions tous les pignons de l'exemple ci-dessus de la même quantité, la sortie resterait la même.)

La chaîne doit être tangente aux pignons sur lesquels elle passe à tous les points de contact, et directement partout ailleurs. La chaîne doit passer sur les pignons de sorte que les segments de chaîne adjacents (c'est-à-dire les parties de la chaîne situées entre deux pignons qui se rejoignent sur le même pignon) ne se croisent pas .

Intersection de la chaîne.

Par exemple, alors que le système de gauche ci-dessus est valide, celui du milieu ne l’est pas, car les deux segments adjacents de la chaîne qui passent au-dessus du pignon inférieur gauche se croisent. Cependant, notez que le bon système est valide, car les deux segments de la chaîne qui se croisent ne sont pas adjacents (ce système est cependant produit par une entrée différente de celle des deux autres).

Pour simplifier les choses (r), vous pouvez supposer qu’aucun pignon ne croise la coque convexe de ses deux pignons voisins, ni les coques convexes de chacun de ses voisins et de leur autre voisin. En d'autres termes, le pignon supérieur du diagramme ci-dessous ne peut croiser aucune des régions ombrées.

Exclusion

Les segments de chaîne peuvent croiser des pignons autres que ceux sur lesquels ils sont passés (comme dans le dernier cas de test). Dans ce cas, la chaîne doit toujours apparaître devant les pignons.

Exigences visuelles

La chaîne devrait consister en une série de maillons de largeurs alternées. La largeur du lien étroit doit être d'environ 2 et la largeur du lien large d' environ 5. La longueur des deux types de liens doit être à peu près égale. La périodede la chaîne, c'est-à-dire la longueur totale d'une paire de liens large / étroite, doit être le nombre le plus proche de 4π qui correspond à un nombre entier de fois dans la longueur de la chaîne. Par exemple, si la longueur de la chaîne est 1 000, sa période doit être de 12,5, soit le nombre le plus proche de 4π (12,566 ...) qui correspond à un nombre entier de fois (80) sur 1 000. Il est important que la période corresponde à un nombre entier de fois dans la longueur de la chaîne, de sorte qu'il n'y ait pas d'artefacts à l'endroit où la chaîne s'enroule.

Chaîne


Un pignon de rayon R devrait être composé de trois parties concentriques: un axe central , qui devrait être un cercle de rayon d'environ 3; le corps du pignon , autour de l'essieu, qui devrait être un cercle de rayon d'environ R - 4.5; et le bord du pignon , autour du corps, qui devrait être un cercle de rayon autour de
R - 1.5. La jante devrait également contenir les dents du pignon , qui devrait avoir une largeur d'environ 4; la taille et l'espacement des dents doivent correspondre à la taille des maillons de la chaîne, afin qu'ils s'emboîtent parfaitement.

Pignon

La période des dents du pignon, c'est-à-dire la distance entre deux dents consécutives le long de la circonférence du pignon, doit correspondre à la période de la chaîne. Étant donné que la période est d'environ 4π et que le rayon du pignon est garanti, la période doit tenir dans la circonférence du pignon un nombre de fois presque entier, de sorte qu'il ne devrait pas y avoir d'artefacts visibles au point où les dents du pignon s'enroulent.

Vous pouvez utiliser n’importe quelle combinaison de couleurs pour la chaîne, les différentes parties du pignon et l’arrière-plan, dans la mesure où elles se distinguent facilement . L'arrière-plan peut être transparent. Les exemples de ce poste s’appliquent Couleur de la chaîne #202020à la chaîne, Couleur de l'axe et de la jante du pignon #868481à l’axe et au rebord Couleur du corps du pignon #646361du pignon et au corps du pignon.

Exigences d'animation

Le premier pignon de la liste d'entrée doit tourner dans le sens des aiguilles d'une montre ; le reste des pignons doit tourner en conséquence. La chaîne devrait se déplacer à une vitesse d'environ 16π (environ 50) unités par seconde; la cadence de prise de vue dépend de vous, mais l'animation doit être suffisamment fluide.

L'animation doit être bouclée de manière transparente .

Conformité

Certains attributs visuels et certaines proportions ne sont spécifiés intentionnellement que grossièrement - vous n'avez pas à les faire correspondre exactement . La sortie de votre programme ne doit pas nécessairement être une réplique, pixel par pixel, des exemples donnés ici, mais elle devrait être similaire. En particulier, les proportions exactes de la chaîne et des pignons, ainsi que la forme exacte des maillons et des dents de la chaîne, sont flexibles.

Les points les plus importants à suivre sont les suivants:

  • La chaîne doit passer sur les pignons, dans l'ordre d'entrée, dans le bon sens.
  • La chaîne doit être tangente aux pignons à tous les points de contact.
  • Les maillons de la chaîne et les dents des pignons doivent être bien enchevêtrés, au moins jusqu’à corriger l’espacement et la phase.
  • L'espacement entre les maillons de la chaîne et les dents des pignons doit être tel qu'il n'y ait pas d'artefacts remarquables à l'endroit où ils s'enroulent.
  • Les pignons doivent tourner dans le bon sens.
  • L'animation doit être bouclée de manière transparente.

Pour terminer, si, techniquement, l'objectif de ce défi est d'écrire le code le plus court, si vous avez envie de faire preuve de créativité et de produire un résultat plus élaboré, ne vous gênez pas!

Défi

Écrivez un programme ou une fonction en prenant une liste de pignons et en produisant une animation du système de transmission par chaîne correspondante, comme décrit ci-dessus.

Entrée et sortie

Vous pouvez utiliser l' entrée via la ligne de commande , via STDIN , sous forme d' arguments de fonction ou à l'aide d'une méthode équivalente . Vous pouvez utiliser n’importe quel format pratique pour l’entrée, mais assurez-vous de le spécifier dans votre message.

En sortie , vous pouvez afficher l'animation directement , créer un fichier d'animation ( un fichier GIF animé, par exemple) ou une séquence de fichiers image (toutefois, il existe une petite pénalité dans ce cas; voir ci-dessous.) assurez-vous que le nombre d'images est raisonnable (les exemples de cet article utilisent très peu d'images;) le nombre d'images ne doit pas nécessairement être minimal, mais vous ne devez pas produire trop d'images superflues. Si vous produisez une séquence d'images, assurez-vous de spécifier la fréquence d'images dans votre message.

But

C'est du code-golf . La réponse la plus courte , en octets, gagne.

+ 10% de pénalité   Si votre programme génère une séquence d'images en sortie, au lieu d'afficher l'animation directement ou de générer un seul fichier d'animation, ajoutez 10% à votre score.

Cas de 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



S'amuser!

Aune
la source
38
Ces gifs sont très satisfaisants +1
Adnan
24
Je serai impressionné si quelqu'un répond avec succès avec n'importe quelle quantité de code.
DavidC
5
Comment as-tu créé les gifs? Et depuis combien de temps cela dure-t-il?
J Atkin
10
@JAtkin De la même manière que tout le monde devrait: j'ai écrit une solution :) Si vous voulez en savoir plus, j'ai utilisé Cairo pour les images individuelles, puis ImageMagick pour créer les gifs (BTW, si quelqu'un veut produire l'animation de cette ainsi, par exemple, par la première génération des cadres, puis en utilisant un outil externe pour les transformer en animation, je suis tout à fait bien avec ça, aussi longtemps que vous spécifiez la dépendance de l'outil dans votre message. pour être clair, il est votre programme qui devrait invoquer l'outil, pas l'utilisateur.)
Ell
5
@Anko La bonne nouvelle est que vous n'avez pas à vous en préoccuper: il est garanti que cette situation ne se produira pas dans l'entrée; voir la partie "pas de pignon intersecte la coque convexe ...", celle avec l'image avec les trois régions ombrées. Plus généralement, la chaîne ne croise chaque pignon qu'une seule fois, selon l'ordre des pignons, même s'il semble passer plus d'une fois à proximité d'un pignon.
Ell

Réponses:

42

JavaScript (ES6), 2557 1915 1897 1681 octets

Ce n'est pas vraiment dupe ! c'est minified - en partie à la main - mais ce n'est rien de spécial. Cela pourrait sans doute être plus court si je l'avais plus joué au golf avant de le faire, mais j'ai déjà passé plus de temps à ce sujet.

Edit: Ok, donc j’ai passé plus de temps dessus et j’ai joué davantage au code avant de minifier (très manuellement cette fois). Le code utilise toujours la même approche et la même structure globale, mais malgré cela, j'ai quand même fini par économiser 642 octets. Pas trop mal, si je le dis moi-même. Probablement raté quelques opportunités d’économie d’octets, mais à ce stade, je ne suis même plus sûr de savoir comment cela fonctionne. La seule chose qui diffère en termes de sortie est qu’il utilise maintenant des couleurs légèrement différentes qui pourraient être écrites de manière plus nuancée.

Edit 2 (beaucoup plus tard): 18 octets enregistrés. Merci à ConorO'Brien dans ses commentaires pour avoir souligné l'aveuglantement évident que j'avais totalement manqué.

Edit 3: J'ai donc pensé inverser mon propre code, car, franchement, je ne me souvenais plus comment je l'avais fait et j'ai perdu les versions non golfées. J'ai donc parcouru et trouvai encore 316 octets supplémentaires à économiser en restructurant et en faisant du 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 fonction ci-dessus ajoute un élément SVG (y compris des animations) au document. Par exemple pour afficher le 2ème cas de test:

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

Semble travailler un régal - au moins ici, dans Chrome.

Essayez-le dans l'extrait de code ci-dessous (en cliquant sur les boutons, vous dessinerez chacun des cas de test d'OP).

Le code dessine la chaîne et les dents de l'engrenage en traits interrompus. Il utilise ensuite des animateéléments pour animer l' stroke-dashoffsetattribut. L'élément SVG résultant est autonome; il n'y a pas d'animation JS ou de style CSS.

Pour que les choses s'alignent bien, la couronne de dents de chaque engrenage est en fait dessinée sous la forme d'une trajectoire composée de deux arcs, de sorte que la trajectoire puisse commencer juste au point tangent où la chaîne se touche. Cela rend beaucoup plus simple de l'aligner.

De plus, il semble y avoir beaucoup d’arrondis lorsqu’on utilise les traits pointillés de SVG. Au moins, c'est ce que j'ai vu; plus la chaîne était longue, plus elle était enchevêtrée dans chaque rapport. Donc, pour minimiser le problème, la chaîne est composée de plusieurs chemins. Chaque chemin consiste en un segment d'arc autour d'un rapport et une ligne droite jusqu'au rapport suivant. Leurs points de décalage sont calculés pour correspondre. La partie "intérieure" fine de la chaîne, cependant, n'est qu'un chemin en boucle, car elle n'est pas animée.

Flambino
la source
2
C'est génial! Félicitations pour avoir répondu à un vieux défi!
Ell
1
-2 octets:R=g=>...
Conor O'Brien
1
@Flambino, j'aime votre solution à ce défi et je suis vraiment désolé que vous avez perdu la source d' origine, je fait un peu enginnering inverse pour le récupérer, il se trouve ici: gist.github.com/micnic/6aec085d63320229a778c6775ec7f9aa aussi je minifiés il manuellement à 1665 octets (il peut être plus minifié, mais je suis paresseux aujourd'hui)
micnic
1
@micnic Merci! Je vais devoir vérifier ça! Et ne vous inquiétez pas, j'ai réussi à faire de l'ingénierie inverse aussi, alors j'ai une version plus lisible. Mais, dang, 16 octets de moins? Gloire! Je vais certainement y jeter un coup d'œil quand je pourrai trouver le temps.
Flambino
1
@ Flambino, l’impact le plus important sur la taille du fichier a été la structure svg, je n’ai pas mis tout ce qui était en a <g>, mais directement dans la racine svg. Également trouvé un endroit où vous avez transformé un drapeau à balayage et un grand drapeau à arc du booléen au nombre à l'aide de 1*x, mais vous pouvez utiliser+x
micnic le
40

C # 3566 octets

Pas du tout joué au golf, mais ça marche (je pense)

Ungolfed dans l'historique d'édition.

Utilise Magick.NET pour rendre 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 a une fonction F; Exemple:

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

entrez la description de l'image ici

TFeld
la source
2
Merci de poster une version golfée! Un petit problème: le premier pignon de votre gif tourne dans le sens contraire des aiguilles d'une montre; le premier pignon doit toujours tourner dans le sens des aiguilles d'une montre.
Ell
Je n'ai vu que C # en passant, mais avez-vous besoin du publicmodificateur avant chaque champ de votre classe?
J Atkin
1
@JAtkin en effet, tout cela est inutile pour autant que je sache. Dans les autres domaines, PointF est vraiment System.Drawing.PointF (similaire pour List, Color et Math), de sorte que les usingclauses correspondantes doivent être incluses ou que les types pleinement qualifiés sont utilisés et que la référence à System.Drawing doit être notée. dans la réponse (si cela doit ajouter au score, je ne sais pas). Réponse impressionnante de toute façon.
VisualMelon
@JAtkin J'ai deux classes, S et P, donc les champs de S sont tous publics. Je ne sais pas si elles sont strictement nécessaires, mais je pense que oui ..
TFeld
3

JavaScript (ES6) 1626 octets

Cette solution est le résultat de l'ingénierie inverse de la solution de @ Flambino, je la poste avec son accord.

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 version non-golfée:

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
la source
1
Vous pouvez enregistrer plus de 250 octets à l'aide de cet outil.