วิธีการหนึ่งที่รวดเร็วและสกปรกใช้แผนกทรงกลม recursive เริ่มต้นด้วยสมการของพื้นผิวโลกแยกสามเหลี่ยมแต่ละรูปแบบซ้ำ ๆ กันจากจุดยอดข้ามไปยังกลางด้านที่ยาวที่สุด (เป็นการดีที่คุณจะแบ่งสามเหลี่ยมออกเป็นสองส่วนเท่ากันหรือพื้นที่เท่ากัน แต่เนื่องจากมันเกี่ยวข้องกับการคำนวณเที่ยวยุ่งยิ่งฉันจึงแยกด้านครึ่งครึ่ง: นี่ทำให้สามเหลี่ยมหลาย ๆ รูปแตกต่างกันเล็กน้อย แต่ ที่ดูเหมือนจะไม่สำคัญสำหรับแอปพลิเคชันนี้)
แน่นอนว่าคุณจะรักษาส่วนย่อยนี้ไว้ในโครงสร้างข้อมูลที่อนุญาตให้ระบุสามเหลี่ยมได้อย่างรวดเร็วว่ามีจุดใดอยู่ ต้นไม้ไบนารี (ตามการโทรซ้ำ) ทำงานได้ดี: ทุกครั้งที่มีการแบ่งสามเหลี่ยมต้นไม้จะถูกแบ่งที่โหนดของสามเหลี่ยมนั้น ข้อมูลเกี่ยวกับระนาบการแยกจะถูกเก็บไว้เพื่อให้คุณสามารถกำหนดจุดใด ๆ ของระนาบใด ๆ ได้อย่างรวดเร็วซึ่งจะกำหนดว่าจะเคลื่อนที่ไปทางซ้ายหรือขวาลงบนต้นไม้
(ฉันบอกว่าการแยก "ระนาบ" หรือไม่ใช่ - ถ้าแบบจำลองพื้นผิวโลกเป็นทรงกลมและใช้พิกัดทางภูมิศาสตร์ (x, y, z) การคำนวณของเราส่วนใหญ่จะเกิดขึ้นในสามมิติซึ่งด้านของสามเหลี่ยมเป็นชิ้นส่วนของ จุดตัดของทรงกลมพร้อมระนาบผ่านจุดกำเนิดทำให้การคำนวณง่ายและรวดเร็ว)
ฉันจะอธิบายด้วยการแสดงขั้นตอนในหนึ่ง octant ของทรงกลม; อีกแปด octants อื่น ๆ จะถูกประมวลผลในลักษณะเดียวกัน octant นั้นเป็นรูปสามเหลี่ยม 90-90-90 ในกราฟิกของฉันฉันจะวาดสามเหลี่ยมแบบยูคลิดที่ทอดยาวตรงมุม: พวกมันดูไม่ค่อยดีจนกว่าพวกมันจะเล็ก แต่ก็สามารถวาดได้ง่ายและรวดเร็ว นี่คือสามเหลี่ยมแบบยุคลิดที่สอดคล้องกับ octant: มันคือจุดเริ่มต้นของขั้นตอน
เนื่องจากทุกด้านมีความยาวเท่ากันเราจึงเลือกแบบสุ่มเป็น "ยาวที่สุด" และแบ่งย่อย:
ทำซ้ำสิ่งนี้สำหรับสามเหลี่ยมแต่ละอันใหม่:
หลังจากขั้นตอนnเราจะมีรูปสามเหลี่ยม 2 ^ n นี่คือสถานการณ์หลังจาก 10 ขั้นตอนแสดง 1024 รูปสามเหลี่ยมใน octant (และ 8192 บนทรงกลมโดยรวม):
เพื่อเป็นภาพประกอบเพิ่มเติมฉันสร้างจุดสุ่มภายในออกเทนนี้และเดินทางต้นไม้การแบ่งย่อยจนกว่าด้านที่ยาวที่สุดของสามเหลี่ยมจะน้อยกว่า 0.05 เรเดียน รูปสามเหลี่ยม (คาร์ทีเซียน) แสดงด้วยจุดโพรบเป็นสีแดง
อนึ่งเพื่อ จำกัด ตำแหน่งของจุดให้แคบลงไปที่ละติจูดหนึ่งระดับ (โดยประมาณ) คุณจะทราบว่านี่เป็นเรเดียน 1/60 และครอบคลุมประมาณ (1/60) ^ 2 / (Pi / 2) = 1/6000 ของ พื้นผิวทั้งหมด เนื่องจากแต่ละแผนกย่อยประมาณครึ่งหนึ่งของขนาดสามเหลี่ยมประมาณ 13 ถึง 14 แผนกย่อยของ octant จะทำเคล็ดลับ นั่นเป็นการคำนวณที่ไม่มาก - อย่างที่เราจะเห็นด้านล่าง - ทำให้มีประสิทธิภาพที่จะไม่เก็บต้นไม้เลย แต่เพื่อทำการแบ่งย่อยทันที ในตอนแรกคุณจะสังเกตได้ว่า octant ใดที่มีจุดอยู่ - ซึ่งถูกกำหนดโดยเครื่องหมายของพิกัดสามจุดซึ่งสามารถบันทึกเป็นเลขฐานสองสามหลัก - และในแต่ละขั้นตอนที่คุณต้องการจดจำว่าจุดนั้นอยู่หรือไม่ ในด้านซ้าย (0) หรือขวา (1) ของรูปสามเหลี่ยม ที่ให้เลขฐานสอง 14 หลักอีก คุณสามารถใช้รหัสเหล่านี้เพื่อจัดกลุ่มคะแนนตามอำเภอใจ
(โดยทั่วไปเมื่อสองรหัสใกล้เคียงกับเลขฐานสองที่เกิดขึ้นจริงจุดที่สอดคล้องกันจะปิดอย่างไรก็ตามคะแนนยังสามารถปิดและมีรหัสที่แตกต่างกันอย่างน่าทึ่งพิจารณาสองจุดห่างกันหนึ่งเมตรแยกเส้นศูนย์สูตรตัวอย่างเช่นรหัสของพวกเขาจะต้องแตกต่างกัน ก่อนจุดไบนารี่เพราะพวกมันอยู่ใน octants ที่แตกต่างกันสิ่งนี้ไม่สามารถหลีกเลี่ยงได้ด้วยการแบ่งพื้นที่คงที่ใด ๆ )
ฉันใช้Mathematica 8เพื่อใช้งานสิ่งนี้: คุณอาจจะใช้มันตามสภาพที่เป็นหรือเป็นรหัสเทียมสำหรับการใช้งานในสภาพแวดล้อมการเขียนโปรแกรมที่คุณโปรดปราน
กำหนดว่าด้านใดของระนาบ 0-ab จุดpอยู่บน:
side[p_, {a_, b_}] := If[Det[{p, a, b}] >= 0, left, right];
ปรับแต่ง abc สามเหลี่ยมตามจุด p
refine[p_, {a_, b_, c_}] := Block[{sides, x, y, z, m},
sides = Norm /@ {b - c, c - a, a - b} // N;
{x, y, z} = RotateLeft[{a, b, c}, First[Position[sides, Max[sides]]] - 1];
m = Normalize[Mean[{y, z}]];
If[side[p, {x, m}] === right, {y, m, x}, {x, m, z}]
]
ตัวเลขสุดท้ายถูกวาดโดยแสดง octant และด้านบนของภาพนั้นโดยการเรนเดอร์รายการต่อไปนี้เป็นชุดของรูปหลายเหลี่ยม:
p = Normalize@RandomReal[NormalDistribution[0, 1], 3] (* Random point *)
{a, b, c} = IdentityMatrix[3] . DiagonalMatrix[Sign[p]] // N (* First octant *)
NestWhileList[refine[p, #] &, {a, b, c}, Norm[#[[1]] - #[[2]]] >= 0.05 &, 1, 16]
NestWhileList
ใช้การดำเนินการซ้ำ ๆ ( refine
) ในขณะที่เงื่อนไขเกี่ยวข้อง (สามเหลี่ยมมีขนาดใหญ่) หรือจนกว่าจะถึงจำนวนการดำเนินการสูงสุด (16)
ในการแสดงสมการเต็มรูปแบบของ octant ฉันเริ่มต้นด้วย octant แรกและทำซ้ำการปรับแต่งสิบครั้ง สิ่งนี้เริ่มต้นด้วยการดัดแปลงเล็กน้อยของrefine
:
split[{a_, b_, c_}] := Module[{sides, x, y, z, m},
sides = Norm /@ {b - c, c - a, a - b} // N;
{x, y, z} = RotateLeft[{a, b, c}, First[Position[sides, Max[sides]]] - 1];
m = Normalize[Mean[{y, z}]];
{{y, m, x}, {x, m, z}}
]
แตกต่างก็คือsplit
ผลตอบแทนทั้งครึ่งหนึ่งของรูปสามเหลี่ยมปัจจัยการผลิตมากกว่าหนึ่งซึ่งในจุดที่กำหนดโกหก การหาสมการแบบเต็มได้จากการวนซ้ำนี้:
triangles = NestList[Flatten[split /@ #, 1] &, {IdentityMatrix[3] // N}, 10];
ในการตรวจสอบฉันคำนวณขนาดของสามเหลี่ยมทุกรูปแล้วดูที่ช่วง ("ขนาด" นี้เป็นสัดส่วนกับร่างที่มีรูปทรงปิรามิดโดยแต่ละสามเหลี่ยมและศูนย์กลางของทรงกลม; สำหรับสามเหลี่ยมเล็ก ๆ เช่นนี้ขนาดนี้จะเป็นสัดส่วนกับพื้นที่ทรงกลมของมัน)
Through[{Min, Max}[Map[Round[Det[#], 0.00001] &, triangles[[10]] // N, {1}]]]
{0.00523, 0.00739}
ดังนั้นขนาดแตกต่างกันไปขึ้นหรือลงประมาณ 25% จากค่าเฉลี่ยของพวกเขา: ดูเหมือนว่าเหมาะสมสำหรับการบรรลุวิธีการประมาณกลุ่มเพื่อจุดที่สม่ำเสมอ
ในการสแกนรหัสนี้คุณจะสังเกตเห็นว่าไม่มีตรีโกณมิติ : มันจะต้องมีที่เดียวเท่านั้นถ้าหากทั้งหมดจะเป็นการแปลงไปมาระหว่างพิกัดทรงกลมและพิกัดคาร์ทีเซียน รหัสนี้ไม่ได้ฉายภาพพื้นผิวโลกบนแผนที่ใด ๆ ดังนั้นจึงหลีกเลี่ยงการบิดเบือนผู้เข้าร่วม มิฉะนั้นจะใช้ค่าเฉลี่ย ( Mean
) เท่านั้นทฤษฎีบทพีทาโกรัส ( Norm
) และดีเทอร์มิแนนต์ 3 คูณ 3 ( Det
) เพื่อทำงานทั้งหมด (มีคำสั่ง list-manipulation ง่ายๆเช่นRotateLeft
และFlatten
พร้อมกับค้นหาด้านที่ยาวที่สุดของสามเหลี่ยมแต่ละอัน)