วิทยาศาสตร์ Sprocket: การเคลื่อนไหวระบบขับเคลื่อนโซ่


96

เป้าหมายของการท้าทายนี้คือการผลิตการเคลื่อนไหวของห่วงโซ่ไดรฟ์ระบบประกอบด้วยชุดของเกียร์เฟืองเชื่อมต่อกันด้วยโซ่

ข้อกำหนดทั่วไป

โปรแกรมของคุณจะได้รับรายชื่อเฟืองซึ่งระบุว่าเป็น(x, y, radius)สามเท่า ระบบห่วงโซ่ไดรฟ์ส่งผลให้ประกอบด้วยเฟืองเหล่านี้เชื่อมต่อกันด้วยโซ่ตึงปิดผ่านแต่ละของพวกเขาในการสั่งซื้อ เป้าหมายของคุณคือสร้างอนิเมชั่นวนลูปไม่ จำกัดแสดงระบบที่กำลังเคลื่อนไหว ตัวอย่างเช่นกำหนดอินพุต

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

ผลลัพธ์ควรมีลักษณะดังนี้

ตัวอย่างที่ 1.

ระบบพิกัดควรเช่นที่จุดแกน x ขวาและแกน y ชี้ขึ้น คุณอาจจะคิดว่ารัศมีมีแม้ตัวเลขที่มากกว่าหรือเท่ากับ 8 (เราจะเห็นว่าทำไมเรื่องนี้ในภายหลัง.) นอกจากนี้คุณยังอาจคิดว่ามีอย่างน้อยสองเฟืองและที่เฟืองไม่ตัดอีกคนหนึ่ง หน่วยของอินพุตไม่สำคัญเกินไป ตัวอย่างและกรณีทดสอบทั้งหมดในโพสต์นี้ใช้พิกเซลเป็นหน่วยอินพุต (ตัวอย่างเช่นรัศมีของเฟืองกลางในรูปก่อนหน้าคือ 24 พิกเซล;) พยายามอย่าเบี่ยงเบนหน่วยเหล่านี้มากเกินไป ในส่วนที่เหลือของความท้าทายปริมาณเชิงพื้นที่จะถูกเข้าใจในหน่วยเดียวกับอินพุต - ตรวจสอบให้แน่ใจว่าได้สัดส่วนที่ถูกต้อง! มิติของการส่งออกควรจะมีขนาดใหญ่กว่ากรอบของเฟืองทั้งหมดที่มีขนาดใหญ่พอเล็กน้อยเพื่อให้ระบบทั้งหมดจะมองเห็นได้ โดยเฉพาะอย่างยิ่งตำแหน่งที่แน่นอนของเฟืองไม่ควรส่งผลกระทบต่อการส่งออก; เฉพาะตำแหน่งสัมพัทธ์ของพวกเขาควร (เช่นถ้าเราเปลี่ยนเฟืองทั้งหมดในตัวอย่างข้างต้นด้วยจำนวนเดียวกันเอาท์พุทจะยังคงเหมือนเดิม)

โซ่ควรสัมผัสกับเฟืองที่ผ่านไปทุกจุดติดต่อและตรงไปที่อื่น ห่วงโซ่ควรผ่านเฟืองดังกล่าวว่ากลุ่มห่วงโซ่ที่อยู่ติดกัน (นั่นคือส่วนหนึ่งของห่วงโซ่ระหว่างสองเฟืองที่พบกันที่เฟืองเดียวกัน) ไม่ตัดแต่ละอื่น ๆ

แยกโซ่.

ตัวอย่างเช่นในขณะที่ระบบด้านซ้ายด้านบนใช้งานได้ แต่ระบบกึ่งกลางไม่ได้อยู่เนื่องจากส่วนของโซ่ทั้งสองที่อยู่ติดกันที่ผ่านทางเฟืองซ้ายล่างด้านล่าง อย่างไรก็ตามโปรดทราบว่าระบบที่ถูกต้องนั้นถูกต้องเนื่องจากเซกเมนต์ลูกโซ่ทั้งสองที่ตัดกันนั้นไม่ได้อยู่ติดกัน (ระบบนี้ผลิตโดยอินพุตที่แตกต่างจากอีกสองเซกเมนต์)

เพื่อให้สิ่งต่าง ๆ เรียบง่าย (r) คุณอาจคิดว่าไม่มีเฟืองเข้ามาขวางลำตัวนูนของเฟืองสองตัวที่อยู่ใกล้เคียงหรือตัวถังนูนของแต่ละเพื่อนบ้านและเพื่อนบ้านอื่น ๆ กล่าวอีกนัยหนึ่งเฟืองบนสุดในแผนภาพด้านล่างอาจไม่สามารถตัดกันบริเวณที่แรเงาได้

ยกเว้น

ส่วนของโซ่อาจตัดเฟืองอื่นนอกเหนือจากที่ผ่าน (เช่นในกรณีทดสอบครั้งสุดท้าย) ในกรณีนี้โซ่ควรปรากฏต่อหน้าเฟืองเสมอ

ข้อกำหนดด้านภาพ

โซ่ควรประกอบด้วยชุดของลิงก์ของความกว้างสลับ ความกว้างของลิงค์แคบควรมีประมาณ 2 และความกว้างของลิงค์กว้างควรมีประมาณ 5 ความยาวของลิงค์ทั้งสองประเภทควรจะเท่ากัน ระยะเวลาของเชนนั่นคือความยาวทั้งหมดของลิงค์คู่ที่กว้าง / แคบควรเป็นจำนวนที่ใกล้เคียงที่สุดถึง4πที่เหมาะกับจำนวนเต็มจำนวนครั้งในความยาวของโซ่ ตัวอย่างเช่นหากความยาวของโซ่เท่ากับ 1,000 ดังนั้นระยะเวลาของมันควรเป็น 12.5 ซึ่งเป็นจำนวนที่ใกล้เคียงที่สุดกับ4π (12.566 ... ) ที่ตรงกับจำนวนเต็ม (80) ใน 1,000 มันเป็นสิ่งสำคัญสำหรับช่วงเวลาที่พอดีจำนวนจำนวนครั้งในความยาวของห่วงโซ่เพื่อให้มีสิ่งประดิษฐ์ที่จุดที่โซ่ล้อมรอบ

โซ่


เฟืองของรัศมีRควรประกอบด้วยสามส่วนศูนย์กลาง: เพลากลางซึ่งควรเป็นวงกลมรัศมีประมาณ 3; ร่างกายเฟืองของรอบเพลาซึ่งควรจะวงกลมรัศมีเกี่ยวกับR - 4.5; และขอบของเฟืองรอบตัวซึ่งควรเป็นรัศมีประมาณ
R - 1.5 ขอบควรมีฟันของเฟืองซึ่งควรมีความกว้างประมาณ 4 ขนาดและระยะห่างของฟันควรตรงกับขนาดของลิงค์โซ่

ล้อฟันเฟื่อง

ระยะเวลาของฟันของเฟืองนั่นคือระยะห่างระหว่างฟันสองซี่ติดต่อกันตามแนวเส้นรอบวงของเฟืองควรตรงกับระยะเวลาของโซ่ เนื่องจากระยะเวลาประมาณ4πและเนื่องจากรัศมีของเฟืองถูกรับประกันว่าจะเท่ากันดังนั้นช่วงเวลาจึงควรพอดีกับเส้นรอบวงของเฟืองที่มีจำนวนเกือบเป็นจำนวนเต็มครั้งดังนั้นจึงไม่ควรมีสิ่งประดิษฐ์ที่สังเกตได้ ณ จุดที่ ฟันของเฟืองรอบตัว

คุณอาจจะใช้การรวมกันของสีใด ๆสำหรับห่วงโซ่ที่ส่วนต่างๆของอลูมิเนียมและพื้นหลังตราบใดที่พวกเขาจะมีความแตกต่างได้อย่างง่ายดาย พื้นหลังอาจโปร่งใส ตัวอย่างในโพสต์นี้ใช้สีโซ่ #202020สำหรับโซ่เฟืองเพลาและขอบสี #868481สำหรับเพลาและขอบสีของร่างกายเฟือง #646361ของเฟืองและสำหรับร่างกายของเฟือง

ความต้องการภาพเคลื่อนไหว

เตอร์เป็นครั้งแรกในรายการการป้อนข้อมูลที่ควรจะหมุนตามเข็มนาฬิกา ; ส่วนที่เหลือของเฟืองควรหมุนตาม โซ่ควรเคลื่อนที่ด้วยความเร็วประมาณ16π (ประมาณ 50) หน่วยต่อวินาที อัตราเฟรมขึ้นอยู่กับคุณ แต่ภาพเคลื่อนไหวควรดูราบรื่นพอ

ภาพเคลื่อนไหวควรห่วงอย่างลงตัว

สอดคล้อง

แอตทริบิวต์และสัดส่วนที่มองเห็นได้บางอย่างมีการระบุอย่างจงใจโดยประมาณเท่านั้น - คุณไม่จำเป็นต้องจับคู่ให้ตรงกันทั้งหมด ผลลัพธ์ของโปรแกรมของคุณไม่จำเป็นต้องเป็นแบบจำลองพิกเซลต่อพิกเซลของตัวอย่างที่ให้ไว้ที่นี่ แต่ควรมีลักษณะคล้ายกัน โดยเฉพาะอย่างยิ่งสัดส่วนที่แน่นอนของโซ่และเฟืองและรูปร่างที่แน่นอนของลิงก์ของโซ่และฟันของเฟืองนั้นมีความยืดหยุ่น

จุดที่สำคัญที่สุดที่ควรติดตามคือ:

  • ห่วงโซ่ควรผ่านเฟืองตามลำดับการป้อนข้อมูลจากทิศทางที่ถูกต้อง
  • โซ่ควรสัมผัสกับเฟืองที่จุดสัมผัสทุกจุด
  • การเชื่อมโยงของโซ่และฟันของเฟืองควร enmesh อย่างเรียบร้อยอย่างน้อยขึ้นไปเพื่อแก้ไขระยะห่างและเฟส
  • ระยะห่างระหว่างการเชื่อมโยงของโซ่และฟันของเฟืองควรเป็นสิ่งที่ไม่มีสิ่งประดิษฐ์ที่เห็นได้ชัดเจน ณ จุดที่พวกเขาล้อมรอบ
  • เฟืองควรหมุนในทิศทางที่ถูกต้อง
  • ภาพเคลื่อนไหวควรวนซ้ำอย่างราบรื่น

ในฐานะที่เป็นโน้ตสุดท้ายในขณะที่ในทางเทคนิคแล้วเป้าหมายของความท้าทายนี้คือการเขียนโค้ดที่สั้นที่สุดหากคุณรู้สึกอยากสร้างสรรค์และสร้างผลงานที่มีความประณีตมากขึ้น

ท้าทาย

เขียนโปรแกรมหรือฟังก์ชั่นรับรายการเฟืองและสร้างภาพเคลื่อนไหวของระบบไดรฟ์ลูกโซ่ที่เกี่ยวข้องดังที่อธิบายไว้ข้างต้น

อินพุตและเอาต์พุต

คุณอาจจะต้องมีการป้อนข้อมูลผ่านบรรทัดคำสั่งผ่านSTDINเป็นอาร์กิวเมนต์ของฟังก์ชันหรือใช้วิธีการเทียบเท่า คุณสามารถใช้รูปแบบที่สะดวกสำหรับอินพุตได้ แต่อย่าลืมระบุไว้ในโพสต์ของคุณ

ขณะที่การส่งออกคุณอาจแสดงภาพเคลื่อนไหวโดยตรง , ผลิตไฟล์ภาพเคลื่อนไหว (เช่นภาพเคลื่อนไหว GIF) หรือผลิตเป็นลำดับของไฟล์กรอบ ( แต่มีโทษขนาดเล็กในกรณีนี้. ดูด้านล่าง) ถ้าคุณใช้การส่งออกไฟล์ ตรวจสอบให้แน่ใจว่าจำนวนเฟรมเหมาะสม (ตัวอย่างในโพสต์นี้ใช้เฟรมน้อยมาก) จำนวนเฟรมไม่จำเป็นต้องน้อยที่สุด แต่คุณไม่ควรสร้างเฟรมที่ฟุ่มเฟือยมากเกินไป หากคุณส่งลำดับของเฟรมให้แน่ใจว่าระบุอัตราเฟรมในโพสต์ของคุณ

คะแนน

นี่คือรหัสกอล์ฟ คำตอบที่สั้นที่สุดในไบต์ชนะ

การลงโทษ + 10%   หากโปรแกรมของคุณสร้างเฟรมเป็นเอาท์พุตแทนที่จะแสดงภาพเคลื่อนไหวโดยตรงหรือสร้างไฟล์ภาพเคลื่อนไหวเดียวให้เพิ่ม 10% ในคะแนนของคุณ

กรณีทดสอบ

ทดสอบ 1

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

ทดสอบ 1

ทดสอบ 2

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

ทดสอบ 2

ทดสอบ 3

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

ทดสอบ 3

ทดสอบ 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)

ทดสอบ 4

ทดสอบ 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)

ทดสอบ 5

ทดสอบ 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)

ทดสอบ 6



มีความสุข!


38
gifs เหล่านี้น่าพอใจมาก +1
Adnan

24
ฉันจะประทับใจถ้าใครตอบคำถามนี้สำเร็จด้วยรหัสจำนวนเท่าใดก็ได้
DavidC

5
คุณทำ gif อย่างไร และนี่นานแค่ไหนในการทำงาน?
J Atkin

10
@JAtkin เช่นเดียวกับที่คนอื่น ๆ ควร: ฉันเขียนวิธีแก้ปัญหา :) ถ้าคุณถามเกี่ยวกับข้อมูลเฉพาะฉันใช้ไคโรสำหรับแต่ละเฟรมแล้วใช้ ImageMagick เพื่อสร้าง gif (BTW ถ้าใครต้องการสร้างอนิเมชั่นนี้ วิธีคือโดยการสร้างเฟรมครั้งแรกและจากนั้นใช้เครื่องมือภายนอกเพื่อเปลี่ยนเป็นภาพเคลื่อนไหวฉันก็ใช้งานได้ดีตราบใดที่คุณระบุการพึ่งพาเครื่องมือในโพสต์ของคุณ โปรแกรมที่ควรเรียกใช้เครื่องมือไม่ใช่ผู้ใช้)
Ell

5
@ Anko ข่าวดีก็คือคุณไม่ต้องกังวลกับมัน: สถานการณ์นี้รับประกันได้ว่าจะไม่เกิดขึ้นในการป้อนข้อมูล; ดูส่วน "ไม่มีเฟืองที่ตัดกับส่วนนูน ... " ส่วนที่มีภาพพร้อมพื้นที่แรเงาทั้งสาม โดยทั่วไปโซ่จะข้ามเฟืองแต่ละอันเพียงครั้งเดียวตามคำสั่งของเฟืองแม้ว่ามันจะดูเหมือนว่ามันผ่านเข้ามาใกล้เฟืองมากกว่าหนึ่งครั้งก็ตาม
Ell

คำตอบ:


42

JavaScript (ES6), 2557 1915 1897 1681 ไบต์

นี่ไม่ใช่ super-duper golfedจริงๆ; มันลดขนาด - บางส่วนด้วยมือ - แต่นั่นไม่มีอะไรพิเศษ ไม่ต้องสงสัยเลยว่าจะสั้นกว่านี้ถ้าฉันเล่นกอล์ฟมากขึ้นก่อนที่จะลดขนาด แต่ฉันใช้เวลา (มากกว่า) เวลานี้แล้ว

แก้ไข: ตกลงดังนั้นฉันใช้เวลากับมันมากขึ้นและเพิ่มรหัสให้มากขึ้นก่อนที่จะย่อขนาด (ด้วยตนเองในเวลานี้) รหัสยังคงใช้วิธีการเดียวกันและโครงสร้างโดยรวม แต่ถึงกระนั้นฉันก็ยังลงเอยด้วยการประหยัด 642 ไบต์ ไม่โทรมเกินไปถ้าฉันพูดอย่างนั้น อาจพลาดโอกาสในการประหยัดจำนวนไบต์ แต่ ณ จุดนี้แม้ว่าฉันจะไม่แน่ใจว่ามันจะทำงานอย่างไรอีกต่อไป สิ่งเดียวที่แตกต่างกันในแง่ของการส่งออกคือตอนนี้มันใช้สีที่แตกต่างกันเล็กน้อยที่สามารถเขียนได้มากขึ้นยิ่งขึ้น

แก้ไข 2 (มากในภายหลัง): บันทึกแล้ว 18 ไบต์ ขอบคุณ ConorO'Brien ในความคิดเห็นที่ชี้ให้เห็นอย่างชัดเจนว่าฉันพลาดไปโดยสิ้นเชิง

แก้ไข 3: ดังนั้นฉันคิดว่าฉันจะสร้างรหัสของตัวเองขึ้นมาเพราะตรงไปตรงมาฉันจำไม่ได้ว่าฉันทำมันไปได้อย่างไร ดังนั้นฉันเลยผ่านไปและดูเถิดและพบอีก 316 ไบต์เพื่อประหยัดโดยการปรับโครงสร้างและทำไมโครกอล์ฟ

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

ฟังก์ชั่นด้านบนผนวกองค์ประกอบ SVG (รวมถึงภาพเคลื่อนไหว) เข้ากับเอกสาร เช่นเพื่อแสดงกรณีทดสอบที่ 2:

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

ดูเหมือนว่าจะได้รับการปฏิบัติอย่างน้อยที่นี่ใน Chrome

ลองในตัวอย่างด้านล่าง (การคลิกปุ่มจะวาดกรณีทดสอบของ OP แต่ละครั้ง)

รหัสดึงโซ่และฟันเฟืองเป็นเส้นประ จากนั้นใช้animateองค์ประกอบต่างๆเพื่อทำให้แอstroke-dashoffsetททริบิวต์เคลื่อนไหว องค์ประกอบ SVG ที่ได้นั้นมีอยู่ในตัวเอง ไม่มีภาพเคลื่อนไหวที่ขับเคลื่อนด้วย JS หรือการใส่สไตล์ CSS

เพื่อให้สิ่งต่าง ๆ เข้ากันได้อย่างดีแหวนฟันของอุปกรณ์แต่ละอันจะถูกวาดเป็นเส้นทางที่ประกอบด้วยสองส่วนโค้งดังนั้นเส้นทางสามารถเริ่มต้นได้ที่จุดสัมผัสที่สัมผัสกับโซ่ สิ่งนี้ทำให้ง่ายขึ้นมากในการจัดแนว

นอกจากนี้ดูเหมือนว่ามีข้อผิดพลาดในการปัดเศษจำนวนมากเมื่อใช้ลายเส้นประของ SVG อย่างน้อยนั่นคือสิ่งที่ฉันเห็น ยิ่งโซ่ยาวมากเท่าไหร่ก็ยิ่งแย่ลงเท่านั้น ดังนั้นเพื่อลดปัญหาห่วงโซ่จึงประกอบด้วยหลายเส้นทาง แต่ละเส้นทางประกอบด้วยเซ็กเมนต์ arcing รอบหนึ่งเกียร์และเส้นตรงไปยังเกียร์ถัดไป เส้นประของพวกเขาจะถูกคำนวณเพื่อให้ตรงกับ อย่างไรก็ตามบางส่วนของ "ห่วงโซ่" ของโซ่เป็นเพียงเส้นทางวนลูปเดียวเนื่องจากมันไม่เคลื่อนไหว


2
ดูดี! ความรุ่งโรจน์สำหรับการตอบคำถามท้าทายเก่า ๆ (ish)!
Ell

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

1
@Flambino ผมชอบวิธีการแก้ปัญหาของคุณสำหรับความท้าทายนี้และผมรู้สึกเสียใจจริงๆที่คุณสูญเสียแหล่งเดิมที่ฉันทำบาง enginnering ย้อนกลับในการกู้คืนมันสามารถพบได้ที่นี่: gist.github.com/micnic/6aec085d63320229a778c6775ec7f9aa ผมก็ minified มัน ถึง 1665 ไบต์ด้วยตนเอง (สามารถลดขนาดได้มากขึ้น แต่วันนี้ฉันขี้เกียจ)
micnic

1
@micnic ขอบคุณ! ฉันจะต้องตรวจสอบว่า! และไม่ต้องกังวลฉันก็สามารถทำวิศวกรรมย้อนกลับได้เช่นกันดังนั้นฉันจึงมีเวอร์ชันที่อ่านได้มากกว่า แต่แดงน้อยกว่า 16 ไบต์? รุ่งโรจน์! แน่นอนฉันจะให้มันดูเมื่อฉันสามารถหาเวลา
Flambino

1
@Flambino เป็นหลักผลกระทบที่ใหญ่ที่สุดในขนาดไฟล์เป็นโครงสร้าง svg ฉันไม่ได้ใส่ evething ใน<g>แต่ใส่โดยตรงในราก svg พบสถานที่ที่คุณเปลี่ยนรูปธงกวาดและธงส่วนโค้งขนาดใหญ่จากบูลีนเป็นตัวเลขโดยใช้1*xแต่คุณสามารถใช้+x
micnic

40

C # 3566 ไบต์

ไม่เล่นกอล์ฟเลย แต่ใช้งานได้ (ฉันคิดว่า)

Ungolfed ในประวัติการแก้ไข

ใช้ Magick.NET เพื่อเรนเดอร์ 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;}}

คลาส P มีฟังก์ชั่น F ตัวอย่าง:

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

ป้อนคำอธิบายรูปภาพที่นี่


2
ขอบคุณที่โพสต์เวอร์ชั่น golfed! การเล่นโวหารเล็กน้อย: เฟืองแรกใน gif ของคุณหมุนทวนเข็มนาฬิกา; เฟืองแรกควรหมุนตามเข็มนาฬิกา
Ell

ฉันเพิ่งเห็น C # ในการผ่าน แต่คุณต้องการpublicตัวแก้ไขก่อนแต่ละฟิลด์ในชั้นเรียนของคุณหรือไม่
J Atkin

1
@JAtkin แน่นอนว่ามันไม่จำเป็นเท่าที่ฉันจะบอกได้ ในเรื่องอื่น ๆ PointF นั้นเป็น System.Drawing.PointF (คล้ายกับ List, Color และ Math) ดังนั้นจึงusingควรรวมคำสั่งที่เกี่ยวข้องไว้หรือประเภทที่ผ่านการรับรองโดยสมบูรณ์เมื่อใช้งานและการอ้างอิงไปยัง System.Drawing ควรสังเกต ในคำตอบ (ไม่ว่าจะควรเพิ่มในคะแนนที่ฉันไม่รู้) คำตอบที่น่าประทับใจ แต่อย่างใด
VisualMelon

@JAtkin ฉันมีสองคลาส S และ P ดังนั้นฟิลด์ใน S จึงเป็นแบบสาธารณะทั้งหมด ถ้าไม่แน่ใจว่าพวกเขามีความจำเป็นอย่างเคร่งครัด แต่ฉันคิดอย่างนั้น ..
TFeld

3

JavaScript (ES6) 1626 ไบต์

โซลูชันนี้เป็นผลมาจากวิศวกรรมย้อนกลับของโซลูชันของ @ Flambino ฉันโพสต์ด้วยความเห็นของเขา

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

เวอร์ชันที่ไม่ดีงาม:

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
คุณสามารถบันทึกมากกว่า 250 ไบต์ใช้นี้เครื่องมือ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.