ฉันจะคำนวณเส้นทางสำหรับวัตถุที่มีความเร่ง จำกัด ได้อย่างไร


9

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

ตัวชี้ไปยังเอกสารประกอบ (หรือแม้แต่ชื่อ) น่าจะดีอย่างสมบูรณ์ - ฉันมีปัญหาในการค้นหาอะไรเลย

ในความพยายามของฉันพวกเขาทำงานในกรณีง่าย ๆ แต่ล้มเหลวอย่างน่าสังเวชในสถานการณ์เช่นเมื่อจุด b อยู่ใกล้กับรัศมีวงเลี้ยวต่ำกว่า

ตัวอย่างเช่นคุณจะกำหนดเส้นทางที่คล้ายกับสิ่งนี้ได้อย่างไร (เส้นทางตัวหนา):

เพียงแค่เส้นทางโค้งเพื่อใช้เป็นตัวอย่าง

แก้ไข: ในปัญหาที่แท้จริงของฉันมีข้อ จำกัด เส้นทางง่าย ๆ แต่ฉันมีอัลกอริธึม A * ที่ใช้งานได้ แต่มันช่วยให้สิ่งต่าง ๆ สามารถเปลี่ยนแปลงหัวเรื่องได้ทันที เล็กน้อยเมื่อพวกเขาไปถึงจุดเปลี่ยน


gamedev.stackexchange.com/questions/86881/แต่ฉันไม่แน่ใจว่าฉันเข้าใจคำตอบเกี่ยวกับวิธีการตั้งค่าพื้นที่ 3 มิติ
xaxxon

"นึกคิดอัลกอริธึมนี้จะสามารถรับมือกับการเปลี่ยนแปลงความเร็ว" รัศมีวงเลี้ยวต่ำสุดเกี่ยวข้องกับความเร็วขณะที่มันเปลี่ยนไปหรือว่ามันคงที่สำหรับรถคันใดคันหนึ่งหรือไม่?
DMGregory

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

แผนภาพเส้นโค้ง Bezier ทำให้ฉันนึกถึงคำตอบอื่น ๆเล็กน้อยเกี่ยวกับการวางแผนเส้นทางด้วยการเร่งความเร็วที่ จำกัดในกรณีนี้ความเร่งถูกจำลองแบบเหมือนจรวดทรัสตีทิศทางแทนที่จะเป็นรัศมีวงเลี้ยว
DMGregory

คำตอบ:


7

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

รถที่มีวงกลมแสดงถึงรัศมีวงเลี้ยว ( ไอคอนรถยนต์ผ่าน Kenney )

จากจุดเริ่มต้นและทิศทางที่กำหนดเราสามารถวาดวงกลมสองวงด้วยรัศมีวงเลี้ยวต่ำสุดของเรา - หนึ่งทางด้านซ้ายหนึ่งวงกลมทางขวา สิ่งเหล่านี้อธิบายถึงจุดเริ่มต้นที่แคบที่สุดที่เป็นไปได้ในเส้นทางของเรา

เราสามารถทำเช่นเดียวกันสำหรับตำแหน่งและทิศทางที่ต้องการ แวดวงเหล่านี้อธิบายจุดสิ้นสุดที่เป็นไปได้ที่แน่นที่สุดสำหรับเส้นทางของเรา

ตอนนี้ปัญหาลดลงในการค้นหาเส้นทางที่เชื่อมต่อหนึ่งในแวดวงเริ่มต้นกับหนึ่งในแวดวงสุดท้ายโดยจูบแต่ละวงตามแทนเจนต์

(นี่เป็นการสมมติว่าเราไม่จำเป็นต้องค้นหาสิ่งกีดขวางในระหว่างนั้นซึ่งไม่ได้กล่าวถึงในคำถามคำตอบของ Stormwind จะเป็นวิธีที่เราสามารถใช้ข้อมูลกราฟนำทางสำหรับปัญหาประเภทนี้เมื่อเรามีลำดับของโหนด ในการผ่านเราสามารถใช้วิธีด้านล่างกับแต่ละส่วนของแผน)

ถ้าเพื่อความเรียบง่ายเราใช้เส้นตรงเราจะได้อะไรเช่นนี้:

ไดอะแกรมแสดงเส้นทางต่าง ๆ ที่รถอาจใช้

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

การคำนวณเส้นทางเหล่านี้

ลองหาเคสสำหรับทิศทางการเลี้ยวเดียวบอกว่าเราเริ่มต้นเส้นทางของเราโดยเลี้ยวขวา

ศูนย์กลางของวงเลี้ยวขวาของเราคือ:

startRightCenter = carStart.position + carStart.right * minRadius

ลองเรียกมุมของส่วนทางตรงของเส้นทางของเรา (วัดจากแกน x บวก) pathAngle

หากเราวาดเวกเตอร์จากrightCenterจุดที่เราออกจากวงเลี้ยว (ซึ่งในจุดที่เราต้องเผชิญกับ pathAngle) แสดงว่าเวกเตอร์นั้นคือ ...

startOffset = minRadius * (-cos(pathAngle), sin(pathAngle))

นั่นหมายความว่าจุดที่เราออกจากวงกลมต้อง ...

departure = startRightCenter + startOffset

จุดที่เราเข้าสู่วงเลี้ยวอีกครั้งขึ้นอยู่กับว่าเราตั้งใจจะจบด้วยเลี้ยวซ้ายหรือเลี้ยวขวา:

// To end with a right turn:
reentry = endRightCenter + startOffset

// To end with a left turn: (crossover)
reentry = endLeftCenter - startOffset

ตอนนี้ถ้าเราได้ทำถูกต้องงานของเราเส้นที่เชื่อมdepartureไปreentryควรจะตั้งฉากกับstartOffset:

dot(reentry - departure,  startOffset) = 0

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

ลองแทนกรณีเลี้ยวขวาไปเลี้ยวขวาเป็นตัวอย่าง:

dot(endRightCenter + startOffset - startRightCenter - startOffset, startOffset) = 0
dot(endRightCenter - startRightCenter, startOffset) = 0
pathAngle = atan2(endRightCenter - startRightCenter)

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

แก้ไข: ปลายทางภายในรัศมีวงเลี้ยวต่ำสุด

มันกลับกลายเป็นว่าวิธีการนี้มักจะทำงานนอกกรอบแม้ในขณะที่ปลายทางอยู่ใกล้กว่าระยะทางเลี้ยวต่ำสุดของเรา อย่างน้อยบางส่วนของหนึ่งในวงกลมที่กลับเข้ามาในรัศมีวงเลี้ยวทำให้เราพบเส้นทางที่มีชีวิตได้ตราบใดที่เราไม่สนใจว่ามันจะเป็นเหมือนเพรทเซล ...

แสดงให้เห็นถึงตัวเลือกเมื่อการวางแผนเส้นทางไปยังปลายทางที่ใกล้เคียง

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


นั่นเป็นวิธีง่ายๆในการคิดเกี่ยวกับมันและการแทนเจนต์บนวงกลมนั้นค่อนข้างง่ายที่จะทำงานด้วย ฉันแค่อ่านคำตอบของคุณจนถึงตอนนี้ แต่มีปัญหาอย่างหนึ่งที่ฉันได้ทำคือถ้าเป้าหมายอยู่ในวงกลมหมุนของจุดเริ่มต้น
xaxxon

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

2
หนึ่งเดือน (และการรบกวนหลายครั้ง) ต่อมาฉันได้ทำงานนี้ ฉันคำนวณ 4 แทนเจนต์ - แทนเจนต์ "ด้านนอก" และ "ภายใน" (หรือ "ข้าม") ดังนั้น start.left_circle ไปยัง target.left_circle, start.left_circle "ข้าม" ไปยัง target.right_circle (จากนั้นอีกสองคนก็เปลี่ยนวงกลม) นี่คือเส้นทาง "ด้านนอก": youtube.com/watch?v=99e5Wm8OKb0และนี่คือเส้นทาง "ข้าม": youtube.com/watch?v=iEMt8mBheZU
xaxxon

1

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

การถ่ายภาพสถานการณ์ที่คล้ายกันจากระบบการจราจรทางน้ำและด้วยสมมติฐานที่ว่า

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

คุณสามารถมีสิ่งที่ชอบด้านล่าง (ยกโทษให้ฉันสำหรับภาพที่ดูเป็นเด็ก)

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

(สี่เหลี่ยมสีแดงคือโหนดเส้นสีแดงเป็นจุดเชื่อมต่อระหว่างกันสมมติว่าคุณใช้ตัวแก้ปัญหาการค้นหาเส้นทางที่ให้โหนด 1-9 เพื่อขับผ่านโหนด 4-9 เห็นในรูปภาพและคุณต้องการผ่านโหนดที่ระบุโดยสายสีเขียว ไปที่โรงรถที่โหนด # 9 อย่างไรก็ตามคุณไม่ต้องการไปที่เส้นสีเขียวอย่างแม่นยำแทนที่จะอยู่ที่เลนด้านขวาตามธรรมชาติ

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

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

P (+1 ถัดไป) เป็นสิ่งจำเป็นในการกำหนดระยะทาง - เมื่อรถไปถึง P (ถัดไป) หรือเข้าไปในรัศมีของข้อมูลเมตาของมันก็สามารถปรับมุมพวงมาลัยได้ขึ้นอยู่กับว่า P อยู่ไกลแค่ไหน (ถัดไป 1) กล่าวคือ ถ้ามันอยู่ใกล้เลี้ยวมากถ้ามันอยู่ไกล ๆ เลี้ยวนิดหน่อย เห็นได้ชัดว่าจำเป็นต้องมีกฎ & เงื่อนไขขอบอื่น ๆ เช่นการคำนวณระยะห่างระหว่างรถและสายช่วยเหลือตามเส้นสัมผัสด้านขวาของ P (ถัดไป) และ P (ถัดไป 1) และการแก้ไขโดยที่ - ความประสงค์ที่จะอยู่บนเส้นประ (เหนือรูป) และเส้นประ (ด้านล่างรูป)

ในกรณีใด ๆ ขณะที่รถผ่านโหนหนึ่งก็ลืมมันและเริ่มที่จะมองไปที่สองคนต่อไป

คำถามของคุณ เห็นได้ชัดว่าเมื่อถึงโหนด 7 (ในรูปข้างต้นมองว่าเป็นโหนด 2 ในภาพด้านล่าง) ก็ไม่สามารถเปิดพอ

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

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

มีสีเขียวเส้นช่วยเหลือ 1,2,3 เมื่อรถมาถึงวงกลมสีแดงอมม่วงมันจะเริ่มหมุนไปทางขวา ณ จุดนี้คุณสามารถคำนวณได้ว่ามันจะไม่สำเร็จ (คุณรู้อัตราการหมุนสูงสุดและสามารถคำนวณเส้นโค้งและสามารถเห็นได้ว่ามันจะข้ามทั้งสายด่วน 2 และ 3) หมุนพวงมาลัยไปทางขวาแล้วปล่อยให้มันขับไปข้างหน้า (โดยการเพิ่มขึ้นของฟิสิกส์) และทำให้ช้าลงเมื่อไปถึงบรรทัดช่วยเหลือ 3 (เข้าใกล้ - ใช้ขีด จำกัด , f (dist to helpline) เป็นต้น) เมื่อมันเป็นที่ความช่วยเหลือสาย 3 ไปสู่การย้อนกลับโหมดเลี้ยวพวงมาลัยไปทางตรงข้ามเต็ม ปล่อยให้มันย้อนกลับจนกว่าจะถึงบรรทัดช่วยเหลือ 4(สายเชื่อมต่อระหว่างโหนด 1 และ 2 - google สำหรับ "จุดที่ด้านข้างของอัลกอริทึมบรรทัด") ช้าลงเมื่อถึงแล้วให้เข้าสู่โหมดขับเคลื่อนข้างหน้าอีกครั้งหมุนวงล้อ ทำซ้ำจนกระทั่งถนนปลอดโปร่ง - เห็นได้ชัดว่ามันเพียงพอกับการเพิ่ม manouver 1 ครั้ง

นี่เป็นแนวคิดทั่วไป: ในระหว่างวนรอบเกมหรือเมื่อตรวจสอบระบบงานเกม que:

  • ตรวจสอบตำแหน่งรถ, ความเร็ว, ฯลฯ มุมกับข้อ จำกัด ในปัจจุบันและขอบประตู ,
  • หากยังไม่ถึงให้ดำเนินการต่อกับสิ่งที่คุณกำลังทำ (ปล่อยให้ฟิสิกส์เคลื่อนไปมารถมีรอบต่อนาทีและเกียร์) แทรกการตรวจสอบใหม่ในระบบ que ของคุณเพื่อให้เกิดขึ้นเช่น 0.1 วินาที
  • ถ้าถึง , calqculate เงื่อนไขใหม่, ตั้งค่าข้อมูลและเริ่มต้น แทรกการตรวจสอบใหม่ที่จะเกิดขึ้นในระบบ que ในตัวอย่าง 0.1 วินาที
  • เสร็จสิ้นวงจรวนรอบ - ทำต่อทำซ้ำ

โดยการให้โหนดและข้อมูลที่เพียงพอของรถยนต์จะมีการเคลื่อนไหวและความต่อเนื่อง

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


ฉันจะใช้เวลาสักครู่เพื่ออ่านคำตอบของคุณ ฉันได้ตั้งค่า pathfinding ทั่วไปไว้แล้วและใช้งานได้ แต่มันช่วยให้วัตถุสามารถเร่งความเร็วได้ไม่ จำกัด ณ จุดใด ๆ
xaxxon

ฉันมีบางสิ่งบางอย่างใกล้เคียงกับที่คุณอธิบาย เส้น "เคลื่อนที่" สีม่วงนั้นสร้างขึ้นโดยสมบูรณ์ในแนวเส้นตรงสองเส้น: youtube.com/watch?v=EyhBhrkmRiY แต่มันไม่ทำงานในสถานการณ์ที่ "ตึง" และเส้นโค้งไม่ได้ใช้สำหรับการบอกเส้นทางจริง
xaxxon

0

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

bool Circle::outer_tangent_to(const Circle & c2, LineSegment & shared_tangent) const {
    if (this->direction != c2.direction) {
        return false;
    }
    if (this->radius != c2.radius) {
        // how to add it: http://mathworld.wolfram.com/Circle-CircleTangents.html
        // just subtract smaller circle radius from larger circle radius and find path to center
        //  then add back in the rest of the larger circle radius
        throw ApbException("circles with different length radius not supported");
    }

    auto vector_to_c2 = c2.center - this->center;
    glm::vec2 normal_to_c2;
    if (this->direction == Circle::CW) {
        normal_to_c2 = glm::normalize(glm::vec2(-vector_to_c2.y, vector_to_c2.x));
    } else {
        normal_to_c2 = glm::normalize(glm::vec2(vector_to_c2.y, -vector_to_c2.x));
    }

    shared_tangent = LineSegment(this->center + (normal_to_c2 * this->radius),
                                 c2.center + (normal_to_c2 * this->radius));
    return true;
}


bool Circle::inner_tangent_to(const Circle & c2, LineSegment & tangent) const {

    if (this->radius != c2.radius) {
        // http://mathworld.wolfram.com/Circle-CircleTangents.html
        // adding this is non-trivial
        throw ApbException("inner_tangents doesn't support circles with different radiuses");
    }

    if (this->direction == c2.direction) {
        // inner tangents require opposing direction circles
        return false;
    }

    auto vector_to_c2 = c2.center - this->center;
    auto distance_between_circles = glm::length(vector_to_c2);

    if ( distance_between_circles < 2 * this->radius) {
//      throw ApbException("Circles are too close and don't have inner tangents");
        return false;
    } else {
        auto normalized_to_c2 = glm::normalize(vector_to_c2);
        auto distance_to_midpoint = glm::length(vector_to_c2) / 2;
        auto midpoint = this->center + (vector_to_c2 / 2.0f);

        // if hypotenuse is oo then cos_angle = 0 and angle = 90˚
        // if hypotenuse is radius then cos_angle = r/r = 1 and angle = 0
        auto cos_angle = radius / distance_to_midpoint;
        auto angle = acosf(cos_angle);

        // now find the angle between the circles
        auto midpoint_angle = glm::orientedAngle(glm::vec2(1, 0), normalized_to_c2);

        glm::vec2 p1;
        if (this->direction == Circle::CW) {
            p1 = this->center + (glm::vec2{cos(midpoint_angle + angle), sin(midpoint_angle + angle)} * this->radius);
        } else {
            p1 = this->center + (glm::vec2{cos(midpoint_angle - angle), sin(midpoint_angle - angle)} * this->radius);
        }

        auto tangent_to_midpoint = midpoint - p1;
        auto p2 = p1 + (2.0f * tangent_to_midpoint);
        tangent = {p1, p2};

        return true;
    }
};

ต่อไปนี้เป็นตัวอย่างภาพยนตร์สองรหัสด้านบน:

นี่คือเส้นทาง "outer": http://youtube.com/watch?v=99e5Wm8OKb0และนี่คือเส้นทาง "ข้าม": http://youtube.com/watch?v=iEMt8mBheZU

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

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