Hızlı ve kirli bir yol, tekrarlayan küresel bir alt bölüm kullanır . Dünya yüzeyinin bir üçgenlemesinden başlayarak, her üçgeni bir tepe noktasından en uzun kenarının ortasına kadar tekrar tekrar bölün. (İdeal olarak üçgeni iki eşit çaplı parçaya veya eşit alan parçasına bölebilirsiniz, ancak bunlar biraz hesaplama içerdiğinden, kenarları tam olarak ikiye böldüm: bu, çeşitli üçgenlerin sonunda biraz değişmesine neden olur, ancak bu uygulama için kritik görünmüyor.)
Tabii ki bu alt bölümü, herhangi bir keyfi noktanın bulunduğu üçgeni hızlı bir şekilde tanımlamaya izin veren bir veri yapısında koruyacaksınız. Bir ikili ağaç (özyinelemeli çağrılara dayalı olarak) güzel çalışır: bir üçgen her bölünüşünde, ağaç bu üçgenin düğümüne bölünür. Yarma düzlemi ile ilgili veriler korunur, böylece herhangi bir keyfi noktanın düzlemin hangi tarafında bulunduğunu hızlı bir şekilde belirleyebilirsiniz: bu, ağacın sola mı yoksa sağa mı gidileceğini belirler.
Evet - eğer dünyanın yüzeyini bir küre olarak modelleyip jeosantrik (x, y, z) koordinatlarını kullanırsanız, hesaplamalarımızın çoğu üçgenin kenarlarının parça olduğu üç boyutta gerçekleşir. kürenin kökeni boyunca düzlemlerle kesişimleri . Bu hesaplamaları hızlı ve kolay hale getirir.)
Prosedürü bir kürenin bir oktanında göstererek açıklayacağım; diğer yedi oktant da aynı şekilde işlenir. Böyle bir oktan 90-90-90 üçgendir. Grafiklerimde, aynı köşeleri kapsayan Öklid üçgenleri çizeceğim: küçülene kadar çok iyi görünmüyorlar, ancak kolayca ve hızlı bir şekilde çizilebilirler. İşte oktata karşılık gelen Öklid üçgeni: prosedürün başlangıcıdır.
Tüm taraflar eşit uzunlukta olduğundan, biri "en uzun" olarak rastgele seçilir ve alt bölümlere ayrılır:
Yeni üçgenlerin her biri için bunu tekrarlayın:
N adımdan sonra 2 ^ n üçgenimiz olacak. İşte 10 adımdan sonra, oktanda 1024 üçgen (ve genel olarak kürede 8192) gösteren durum:
Başka bir örnek olarak, bu oktan içinde rastgele bir nokta oluşturdum ve üçgenin en uzun tarafı 0.05 radyandan daha azına ulaşana kadar alt bölüm ağacını dolaştım. (Kartezyen) üçgenler prob noktası kırmızı ile gösterilir.
Bu arada, bir noktanın konumunu bir enlem derecesine (yaklaşık) daraltmak için, bunun yaklaşık 1/60 radyan olduğunu ve böylece (1/60) ^ 2 / (Pi / 2) = 1/6000 toplam yüzey. Her alt bölüm yaklaşık olarak üçgen boyutunu yarıya indirdiğinden, oktanın yaklaşık 13 ila 14 alt bölümü hile yapacaktır. Bu çok fazla hesaplama değil - aşağıda göreceğimiz gibi - ağacı hiç saklamamayı değil, altbölümü anında gerçekleştirmeyi verimli hale getiriyor. Başlangıçta, noktanın hangi oktantta yattığını not edersiniz - bu, üç basamaklı bir ikili sayı olarak kaydedilebilen üç koordinatının işaretleri ile belirlenir - ve her adımda noktanın yalan söyleyip söylemediğini hatırlamak istersiniz üçgenin solunda (0) veya sağında (1). Bu başka bir 14 haneli ikili sayı verir. Bu kodları rasgele noktaları gruplamak için kullanabilirsiniz.
(Genellikle, iki kod gerçek ikili sayılara yakın olduğunda, karşılık gelen noktalar yakındır; ancak, noktalar hala yakın olabilir ve oldukça farklı kodlara sahip olabilir. Örneğin, Ekvator'u bir metre aralıklarla ayıran iki noktayı düşünün: kodları bile farklı olmalıdır çünkü farklı oktanlar içindedir. Bu tür bir şey uzayın sabit bir şekilde bölünmesi ile kaçınılmazdır.)
Bunu uygulamak için Mathematica 8'i kullandım : en sevdiğiniz programlama ortamındaki bir uygulama için olduğu gibi veya sözde kod olarak alabilirsiniz.
Düzlem 0-ab noktası p'nin hangi tarafında bulunduğunu belirleyin :
side[p_, {a_, b_}] := If[Det[{p, a, b}] >= 0, left, right];
Üçgen abc noktasını p noktasına göre daraltın.
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}]
]
Son şekil, oktanı görüntüleyerek ve bunun üzerine, aşağıdaki listeyi bir çokgen kümesi olarak oluşturarak çizildi:
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
bir durum söz konusu olduğunda (üçgen büyüktür) veya maksimum çalışma sayısına ulaşılana kadar bir işlemi ( ) tekrar tekrar uygular (16).
Oktanın tam üçgenlemesini göstermek için ilk oktanla başladım ve arıtmayı on kez tekrarladım. Bu, aşağıdakilerin hafif bir modifikasyonu ile başlar 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}}
]
Fark, belirli bir noktanın bulunduğu üçgen yerine giriş üçgeninin her iki yarısını da split
döndürmesidir . Tam nirengi, bunun tekrarlanmasıyla elde edilir:
triangles = NestList[Flatten[split /@ #, 1] &, {IdentityMatrix[3] // N}, 10];
Kontrol etmek için, her üçgenin büyüklüğünün bir ölçüsünü hesapladım ve aralığa baktım. (Bu "boyut", her bir üçgen ve kürenin merkezi tarafından incelenen piramit şeklindeki şekil ile orantılıdır; bunlar gibi küçük üçgenler için, bu boyut esas olarak küresel alanı ile orantılıdır.)
Through[{Min, Max}[Map[Round[Det[#], 0.00001] &, triangles[[10]] // N, {1}]]]
{0.00523, 0.00739}
Böylece, boyutlar ortalamalarından yaklaşık% 25 oranında artar veya azalır: bu, grup noktalarına yaklaşık olarak eşit bir yol elde etmek için makul görünüyor.
Bu kodu tararken, hiçbir trigonometri görmezsiniz : ihtiyaç duyduğu tek yer, eğer varsa, küresel ve kartezyen koordinatlar arasında ileri geri dönüştürme olacaktır. Kod ayrıca dünyanın yüzeyini herhangi bir haritaya yansıtmaz, böylece eşlik eden bozulmaları önler. Aksi takdirde, tüm işi yapmak için sadece ortalama ( Mean
), Pisagor Teoremi ( Norm
) ve 3'e 3 belirleyici ( Det
) kullanır. ( Her üçgenin en uzun tarafının aranmasıyla birlikte RotateLeft
ve gibi bazı basit liste işleme komutları vardır Flatten
.)