Ilmu Sproket: Animasi Sistem Drive Rantai


97

Tujuan dari tantangan ini adalah untuk menghasilkan animasi sistem rantai drive , yang terdiri dari satu set roda gigi sproket yang dihubungkan bersama oleh rantai .

Persyaratan Umum

Program Anda akan diberi daftar sprocket , ditentukan sebagai (x, y, radius)kembar tiga. Sistem penggerak rantai yang dihasilkan terdiri dari sprocket ini, dihubungkan bersama oleh rantai tegang tertutup yang melewati masing-masing, secara berurutan . Tujuan Anda adalah untuk menghasilkan animasi looping tanpa batas , yang menunjukkan sistem bergerak. Misalnya diberi input

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

, hasilnya akan terlihat seperti

Contoh 1.

Sistem koordinat harus sedemikian rupa sehingga sumbu x mengarah ke kanan, dan sumbu y mengarah ke atas. Anda dapat mengasumsikan bahwa jari-jari bahkan bilangan lebih besar dari atau sama dengan 8 (kita akan melihat mengapa ini penting nanti.) Anda juga dapat berasumsi bahwa setidaknya ada dua sprocket , dan sprocket tidak saling berpotongan . The unitdari input tidak terlalu kritis. Semua contoh dan uji kasus dalam posting ini menggunakan piksel sebagai unit input (jadi, misalnya, jari-jari sproket tengah pada gambar sebelumnya adalah 24 piksel;) cobalah untuk tidak menyimpang terlalu banyak dari unit ini. Di sisa tantangan, kuantitas spasial dipahami diberikan dalam satuan yang sama dengan input — pastikan untuk menjaga proporsinya dengan benar! The dimensi output harus sedikit lebih besar dari kotak berlari dari semua sprockets, cukup besar sehingga seluruh sistem terlihat. Secara khusus, posisi absolut sprocket tidak boleh memengaruhi output; hanya posisi relatif mereka yang seharusnya (jadi, misalnya, jika kita menggeser semua sprocket pada contoh di atas dengan jumlah yang sama, hasilnya akan tetap sama.)

Rantai harus bersinggungan dengan sprocket yang dilewatinya di semua titik kontak, dan lurus ke tempat lain. Rantai harus melewati sprocket sehingga segmen rantai yang berdekatan (yaitu, bagian rantai antara dua sprocket, yang bertemu pada sproket yang sama) tidak saling berpotongan .

Persimpangan rantai.

Misalnya, sementara sistem kiri di atas valid, yang di tengah tidak, karena dua segmen rantai yang berdekatan yang melewati sproket kiri bawah berpotongan. Namun, perhatikan bahwa sistem yang tepat adalah valid, karena dua segmen rantai yang bersilangan tidak berdekatan (sistem ini diproduksi oleh input yang berbeda dari dua lainnya, meskipun.)

Untuk menjaga hal-hal sederhana (r), Anda dapat mengasumsikan bahwa tidak ada sproket memotong lambung cembung dari dua sprocket tetangga, atau lambung cembung dari masing-masing tetangga dan tetangga lainnya. Dengan kata lain, sproket atas pada diagram di bawah ini mungkin tidak berpotongan dengan daerah yang diarsir.

Pengecualian

Segmen rantai dapat memotong sprocket selain yang dilewati (seperti pada test case terakhir). Dalam hal ini, rantai harus selalu muncul di depan sprocket.

Persyaratan Visual

Rantai harus terdiri dari serangkaian tautan dengan lebar bergantian. Lebar tautan sempit harus sekitar 2, dan lebar tautan lebar harus sekitar 5. Panjang kedua jenis tautan harus kira-kira sama. The periodedari rantai, yaitu total panjang pasangan lebar / sempit dari tautan, harus merupakan bilangan terdekat dengan 4π yang cocok dengan bilangan bulat bilangan kali dalam panjang rantai. Misalnya, jika panjang rantai adalah 1.000, maka periode harus 12,5, yang merupakan angka terdekat dengan 4π (12.566 ...) yang sesuai dengan jumlah bilangan bulat (80) dalam 1.000. Penting bagi periode untuk memasukkan bilangan bulat berapa kali dalam panjang rantai, sehingga tidak ada artefak pada titik di mana rantai membungkus.

Rantai


Sproket jari-jari R harus terdiri dari tiga bagian konsentris: poros tengah , yang harus berupa lingkaran jari-jari sekitar 3; tubuh sproket , di sekitar poros, yang seharusnya berupa lingkaran jari-jari sekitar R -4.5; dan pelek sproket , di sekitar tubuh, yang seharusnya menjadi lingkaran jari-jari sekitar
R - 1.5. Pelek juga harus mengandung gigi sproket , yang harus memiliki lebar sekitar 4; ukuran dan jarak gigi harus sesuai dengan ukuran mata rantai, sehingga ikatannya rapi.

Sproket

Periode gigi sproket, yaitu jarak antara dua gigi berurutan di sepanjang sproket, harus sesuai dengan periode rantai. Karena periode sekitar 4π, dan karena jari-jari sproket dijamin genap, periode tersebut harus sesuai dengan keliling sproket dalam jumlah yang hampir bilangan bulat, sehingga tidak boleh ada artefak yang terlihat pada titik di mana gigi sproket membungkus.

Anda dapat menggunakan kombinasi warna apa saja untuk rantai, bagian sproket yang berbeda, dan latar belakang, selama mereka mudah dibedakan . Latar belakang mungkin transparan. Contoh-contoh dalam posting ini digunakan Warna Rantai #202020untuk rantai, Gandar Sproket dan Warna Pelek #868481untuk poros dan pelek Warna Tubuh Sproket #646361sproket , dan untuk tubuh sproket.

Persyaratan Animasi

The sproket pertama dalam daftar masukan harus memutar searah jarum jam ; sisa sprocket harus berputar sesuai. Rantai harus bergerak dengan kecepatan sekitar 16π (sekitar 50) unit per detik; kecepatan bingkai terserah Anda, tetapi animasi harus terlihat cukup halus.

Animasi harus diulang mulus .

Kesesuaian

Beberapa atribut dan proporsi visual sengaja ditentukan hanya secara kasar — ​​Anda tidak harus mencocokkannya dengan tepat . Output program Anda tidak harus berupa replika pixel-ke-pixel dari contoh-contoh yang diberikan di sini, tetapi harus terlihat serupa. Secara khusus, proporsi yang tepat dari rantai dan sprocket, dan bentuk yang tepat dari rantai dan gigi sproket, fleksibel.

Poin paling penting untuk diikuti adalah:

  • Rantai harus melewati sprocket, dalam urutan input, dari arah yang benar.
  • Rantai harus bersinggungan dengan sprocket di semua titik kontak.
  • Tautan rantai dan gigi sprocket harus terpasang dengan rapi, setidaknya hingga memperbaiki jarak dan fase.
  • Jarak antara mata rantai, dan gigi sprocket, harus sedemikian rupa sehingga tidak ada artefak yang terlihat pada titik di mana mereka membungkus.
  • Sprocket harus berputar ke arah yang benar.
  • Animasi harus diulang mulus.

Sebagai catatan terakhir, sementara, secara teknis, tujuan dari tantangan ini adalah menulis kode terpendek, jika Anda merasa ingin menjadi kreatif dan menghasilkan output yang lebih rumit, tentu saja, lakukanlah!

Tantangan

Tulis program atau fungsi , ambil daftar sprocket, dan buat animasi sistem drive rantai yang sesuai, seperti dijelaskan di atas.

Masukan dan keluaran

Anda dapat mengambil input melalui baris perintah , melalui STDIN , sebagai argumen fungsi , atau menggunakan metode yang setara . Anda dapat menggunakan format yang sesuai untuk input, tetapi pastikan untuk menentukannya di posting Anda.

Sebagai output , Anda dapat menampilkan animasi secara langsung , menghasilkan file animasi (misalnya, GIF animasi), atau menghasilkan urutan file bingkai (namun, ada penalti kecil dalam kasus ini; lihat di bawah.) Jika Anda menggunakan output file, pastikan jumlah frame masuk akal (contoh dalam posting ini menggunakan sangat sedikit frame;) jumlah frame tidak harus minimal, tetapi Anda tidak harus menghasilkan terlalu banyak frame berlebihan. Jika Anda menampilkan urutan bingkai, pastikan untuk menentukan kecepatan bingkai dalam posting Anda.

Skor

Ini adalah kode-golf . The jawaban terpendek , dalam byte, menang.

+ 10% Penalti   Jika program Anda menghasilkan urutan frame sebagai output, alih-alih menampilkan animasi secara langsung atau menghasilkan file animasi tunggal, tambahkan 10% ke skor Anda.

Uji Kasus

Tes 1

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

Tes 1

Tes 2

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

Tes 2

Tes 3

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

Tes 3

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

Tes 4

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

Tes 5

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

Tes 6



Selamat bersenang-senang!


38
Gif ini sangat memuaskan +1
Adnan

24
Saya akan terkesan jika ada yang berhasil menjawab ini dengan sejumlah kode.
DavidC

5
Bagaimana Anda membuat gif? Dan sudah berapa lama ini bekerja?
J Atkin

10
@JAtkin Cara yang sama seperti orang lain: Saya menulis solusi :) Jika Anda bertanya tentang spesifikasinya, saya menggunakan Kairo untuk masing-masing frame, dan kemudian menggunakan ImageMagick untuk membuat gifs (BTW, jika ada yang ingin membuat animasi ini) cara, yakni dengan pertama menghasilkan frame dan kemudian menggunakan alat eksternal untuk mengubahnya menjadi animasi, aku benar-benar baik-baik saja dengan itu, selama Anda menentukan ketergantungan pada alat dalam posting Anda. Hanya untuk menjadi jelas, itu Anda program yang harus memanggil alat, bukan pengguna.)
Ell

5
@Anko Berita baiknya adalah Anda tidak perlu khawatir: situasi ini dijamin tidak akan terjadi pada input; lihat bagian "no sproket memotong lambung cembung ...", bagian dengan gambar dengan tiga daerah yang diarsir. Secara lebih umum, rantai hanya melewati masing-masing sproket, sesuai dengan urutan sproket, meskipun terlihat seperti melewati sproket lebih dari satu kali.
Ell

Jawaban:


42

JavaScript (ES6), 2557 1915 1897 1681 byte

Ini bukan super-duper golfed benar-benar; itu diperkecil - sebagian dengan tangan - tapi itu tidak istimewa. Tidak diragukan lagi akan lebih pendek jika saya golf lebih banyak sebelum minifying, tapi saya sudah menghabiskan (lebih dari) cukup waktu untuk ini.

Sunting: Oke, jadi saya menghabiskan lebih banyak waktu di sana dan memutarkan kode lebih banyak sebelum minifying (kali ini sangat manual). Kode masih menggunakan pendekatan yang sama dan struktur keseluruhan, tetapi meskipun demikian saya akhirnya menghemat 642 byte. Tidak terlalu buruk, jika saya mengatakannya sendiri. Mungkin melewatkan beberapa peluang penghematan byte, tetapi pada titik ini bahkan saya tidak yakin cara kerjanya lagi. Satu-satunya hal yang berbeda dalam hal output, adalah bahwa sekarang menggunakan warna yang sedikit berbeda yang dapat ditulis lebih singkat.

Sunting 2 (lebih lama): Disimpan 18 byte. Terima kasih kepada ConorO'Brien dalam komentarnya karena menunjukkan dengan sangat jelas bahwa aku benar-benar ketinggalan.

Sunting 3: Jadi, saya pikir saya akan merekayasa ulang kode saya sendiri, karena, terus terang, saya tidak ingat bagaimana saya melakukannya, dan saya kehilangan versi yang tidak dikoleksi. Jadi saya pergi, dan lihatlah ada 316 byte lagi untuk diselamatkan dengan merestrukturisasi dan melakukan beberapa 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}))})}}

Fungsi di atas menambahkan elemen SVG (termasuk animasi) ke dokumen. Misalnya untuk menampilkan test case ke-2:

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

Tampaknya berhasil - setidaknya di sini di Chrome.

Cobalah di snippet di bawah ini (mengklik tombol akan menarik masing-masing kasus uji OP).

Kode ini menarik rantai dan gigi gigi sebagai goresan putus-putus. Kemudian menggunakan animateelemen untuk menghidupkan stroke-dashoffsetatribut. Elemen SVG yang dihasilkan adalah mandiri; tidak ada animasi berbasis JS atau gaya CSS.

Untuk membuat hal-hal berbaris dengan baik, cincin gigi masing-masing gigi sebenarnya digambarkan sebagai jalur yang terdiri dari dua busur, sehingga jalur dapat mulai tepat pada titik singgung di mana rantai menyentuh. Ini membuatnya lebih mudah untuk mengaturnya.

Selain itu, tampaknya ada banyak kesalahan pembulatan saat menggunakan stroke putus-putus SVG. Setidaknya, itulah yang saya lihat; semakin lama rantai, semakin buruk jala dengan setiap gigi berurutan. Jadi untuk meminimalisir masalah, rantai sebenarnya terdiri dari beberapa jalur. Setiap jalur terdiri dari segmen busur di sekitar satu gigi dan garis lurus ke gigi berikutnya. Dash-off mereka dihitung agar sesuai. Namun, bagian "rantai" tipis dari rantai itu hanya jalur pengulangan tunggal, karena tidak dianimasikan.


2
Tampak hebat! Kudos untuk menjawab tantangan lama (ish)!
Ell

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

1
@ Flambino, saya suka solusi Anda untuk tantangan ini dan saya benar-benar menyesal Anda kehilangan sumber asli, saya membuat beberapa rekayasa terbalik untuk memulihkannya, dapat ditemukan di sini: gist.github.com/micnic/6aec085d63320229a778c6775ec7f9aa juga saya telah memperkecilnya secara manual ke 1665 byte (dapat diperkecil lebih banyak, tapi saya malas hari ini)
micnic

1
@ micnic, terima kasih! Saya harus memeriksanya! Dan jangan khawatir, saya berhasil merekayasa balik juga, jadi saya memiliki versi yang lebih mudah dibaca. Tapi, sial, kurang 16 byte? Pujian! Saya pasti akan melihatnya ketika saya dapat menemukan waktu
Flambino

1
@ Flambino, pada dasarnya dampak terbesar pada ukuran file adalah struktur svg, saya tidak memasukkan evething ke dalam <g>, tetapi meletakkannya langsung di svg root. Juga menemukan tempat di mana Anda mengubah bendera sapu dan bendera busur besar dari boolean ke menggunakan angka 1*x, tetapi Anda bisa menggunakan+x
micnic

40

C # 3566 byte

Tidak bermain golf sama sekali, tetapi bekerja (saya pikir)

Tidak disatukan dalam riwayat edit.

Menggunakan Magick.NET untuk merender 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;}}

Kelas P memiliki fungsi F; Contoh:

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

masukkan deskripsi gambar di sini


2
Terima kasih telah memposting versi golf! Berdalih minor: sproket pertama di gif Anda berputar berlawanan arah jarum jam; sproket pertama harus selalu berputar searah jarum jam.
Ell

Saya hanya melihat C # secara sepintas, tetapi apakah Anda memerlukan publicpengubah sebelum setiap bidang di kelas Anda?
J Atkin

1
@ Aytkin memang, semua itu tidak perlu sejauh yang saya tahu. Dalam hal lain, PointF benar-benar System.Drawing.PointF (mirip untuk Daftar, Warna, dan Matematika), sehingga usingklausa yang sesuai harus dimasukkan, atau jenis yang sepenuhnya memenuhi syarat saat digunakan, dan referensi untuk System.Drawing harus diperhatikan dalam jawaban (apakah harus menambah skor saya tidak tahu). Bagaimanapun jawaban yang mengesankan.
VisualMelon

@JAtkin Saya punya dua kelas, S dan P, jadi bidang di S semuanya publik. Tidak yakin apakah mereka benar-benar dibutuhkan, tapi saya pikir begitu ..
TFeld

3

JavaScript (ES6) 1626 byte

Solusi ini adalah hasil rekayasa balik dari solusi @ Flambino, saya mempostingnya atas kemauannya.

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

Versi 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
Anda dapat menyimpan lebih dari 250 byte menggunakan alat ini .
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.