Fortune Algoritması ile Voronoi Diyagramı Oluşturmak
(redpenguin101.github.io)- Fortune’s Algorithm, Voronoi diyagramını O(n log n) sürede oluşturabilir; ancak uygulaması zor olduğundan, büyük diyagramları tekrar tekrar üretmek gerekmiyorsa O(n²) bir uygulama ya da bir kütüphane kullanmak daha gerçekçidir
- Voronoi diyagramı, düzlemi birden çok site noktasına göre en yakın bölgelere ayırır ve sınırlar, iki site noktasına eşit uzaklıktaki noktalar tarafından oluşur
- Algoritma, soldan sağa hareket eden bir sweep line ile parabol yaylarından oluşan cephe hattı beachline'ı korur ve yalnızca site event ile circle event'leri işler
- site event, yeni bir yay ekleyerek mevcut bir yayı böler ve incomplete edge oluşturur; circle event ise ortadaki yayı kaldırırken Voronoi Vertex ve half edge'leri tamamlar
- Pratik bir uygulamada event queue, beachline, incomplete edge haritası ve DCEL birlikte ele alınmalıdır; geçersiz circle event'leri kaldırmak ve kalan edge'leri temizlemek karmaşıklığı ciddi biçimde artırır
Uygulama zorluğu ve kullanım kapsamı
- Fortune’s Algorithm, Voronoi diyagramını O(n log n) zamanda üreten bir algoritmadır
- Gerçek kullanım amacı varsa, doğrudan uygulamaya başlamadan önce gerekli ölçeği değerlendirmek daha iyidir
- Saniyede birden fazla büyük diyagram üretmeniz gerekmiyorsa, O(n²) bir uygulama daha kolay bir seçenek olabilir
- Daha gerçekçi bir alternatif, mevcut bir kütüphane kullanmaktır
- Algoritmanın çalışma sonucu görsel olarak ilgi çekicidir, ancak uygulama süreci zordur ve sık sık hayal kırıklığı yaratabilir
Voronoi diyagramının temel kavramı
- Voronoi diyagramı, düzlemi birden çok bölgeye ayırma yöntemidir ve prosedürel harita üretiminde sık kullanılır
- Girdi olarak seçilen noktalara site veya seed denir
- Her site'a karşılık gelen cell, düzlemde o site'a en yakın olan noktaların kümesidir
- Hücre sınırları, iki site'a eşit uzaklıktaki noktalardan oluşur
- Hücre köşelerinin buluştuğu Voronoi Vertex, üç site'a eşit uzaklıktaki noktadır
sweep line, beachline, event
- Fortune’s Algorithm, soldan sağa hareket eden dikey bir doğru olan sweep line kullanır
- sweep line bir site ile karşılaştığında, odağı o site olan bir parabol yayı oluşur; sweep line uzaklaştıkça yay büyür
- Farklı site'lara ait iki yayın kesiştiği nokta, iki site'a eşit uzaklıkta olduğu için hücre sınırı olur
- İki sınır birleştiğinde diyagramın bir köşesi oluşur
- Etkin yayların cephe hattına beachline denir
- Gerçek uygulama, sweep line'ı piksel piksel ilerletmek yerine yalnızca hesaplanabilir belirli noktalar olan event'leri işler
- site event: Önceden bilinen site koordinatlarıyla tanımlanır; işlendiğinde beachline'a yeni bir yay eklenir
- circle event: beachline üzerindeki üç yayla tanımlanır; işlendiğinde bir yay kaldırılır ve Voronoi Vertex ile half edge oluşturulur
Parabollerle sınır bulmak
- Algoritmada paraboller, alışılmış
y = ax^2 + bx + cbiçimi yerine locus definition ile ele alınır - Bir parabol, bir focus point ve bir directrix ile tanımlanır
- focus point, site olur
- directrix, sweep line olur
- Aynı sweep line'ı directrix olarak kullanan iki parabolün kesişim noktası, iki site'a eşit uzaklıktadır
- Bu nedenle iki parabolün kesişimini bulmak, iki site arasındaki equiedge'i bulmayı sağlar
- Parabolün x koordinatını hesaplayan sözde kod ile, sweep line konumu değiştikçe iki parabolün kesişiminin sınır boyunca nasıl hareket ettiğini gösteren örnek kullanılır
beachline gösterimi ve site event işleme
- beachline üzerindeki her yay, yalnızca ilgili site'ın koordinatlarıyla ifade edilebilir
- sweep line tüm yaylar için ortaktır
- Uygulamada yaylar ayrı nesneler yerine 2D koordinatlar olarak ele alınır
- beachline, noktaların basit bir sırası olarak temsil edilebilir
- Örnek:
[arc1, arc2],[arc1, arc2, arc3] - Aynı site'a ait yaylar beachline üzerinde birden fazla kez görünebilir
- Örnek:
[arc1, arc3, arc1, arc2]
- Örnek:
- Bir site event oluştuğunda, yeni site'dan sola doğru bir çizgi çekildiğinde karşılaşılan beachline yayı bulunur ve yeni yay bu yayı böler
- Yeni site
L, mevcut beachline[.., i, j, k, ..]içindekijyayını bölerse yapı[.., i, j, L, j, k, ..]olur - site'lar x koordinatına göre sırayla kuyruğa girer ve her işleme sonrasında beachline ile event adayları güncellenir
circle event ve circumcircle
- beachline üzerindeki
[.., i, j, k, ..]üç yayında iki sınırın birleştiği bir durum oluşursa ortadakijyayı kaybolur - Bu sırada, üç site'dan geçen bir circumcircle vardır ve çemberin merkezi üç site'a eşit uzaklıktadır
- circumcircle merkezi, Voronoi Vertex olur
- circle event, çemberin sağ uç noktası olan circle point temel alınarak event queue'ya yerleştirilir
- Yeni bir site, circle point'e ulaşmadan önce çemberin içinde bulunursa mevcut circle event geçersiz hale gelir
- Çünkü yeni site önce ortadaki yayı böler ve üç yay kombinasyonu artık korunmaz
- Eski
i, j, küçlüsü kaybolur; bunun yerinei, j, L,L, j, kgibi yeni üçlüler incelenmelidir
incomplete edge ve half edge
- incomplete edge, bir ucu sabitlenmiş, diğer ucu ise iki parabol yayının kesişimiyle tanımlanan bir doğrudur
- Yeni bir yay site event ile eklendiğinde iki incomplete edge oluşur
- Sabit nokta, yeni yayın mevcut beachline ile buluştuğu koordinattır
- Yeni yay
j, mevcut yayi'yi bölerse[i, j],[j, i]kesişimlerine karşılık gelen edge'ler oluşur
- Bir circle event sırasında iki incomplete edge çarpışırsa bu çarpışma noktası Voronoi Vertex olur
- Mevcut incomplete edge'ler bu noktada half edge olarak tamamlanır ve artık komşu hale gelen iki yay arasında yeni bir incomplete edge oluşturulur
Yalnızca saat yönünün tersine olan çemberler circle event olur
- beachline üzerinde
[i, j, k, j, i]varsaijkvekjiher ikisi de bir çember oluşturabilir, ancak ikisi de geçerli circle event değildir - Ortadaki yayın kaybolduğu durum, yalnızca sınırların gerçekten yakınsadığı taraftır
- Programda üç noktanın orientation'ı determinant ile belirlenir
- determinant negatifse saat yönünün tersinedir ve bir circle event olur
- determinant pozitifse saat yönündedir ve circle event değildir
- determinant 0 ise üç nokta doğrusaldır ve çember yoktur
Algoritmanın genel akışı
- Girdi site'ları x koordinatına göre sıralayıp site event olarak kuyruğa koyun
- Kuyruk boşalana kadar sıradaki event'i çıkarıp işleyin
- site event işleme:
- Henüz gerçekleşmemiş circle event'ler içinde yeni site'ın çemberin içine girdiği event'leri kaldırın
- Yeni site'ın böleceği beachline yayını bulun
- Yeni yayı ekleyip mevcut yayı bölün
- İki incomplete edge ekleyin
- Yeni oluşan üçlülerin circle event üretip üretemeyeceğini kontrol edin
- circle event işleme:
- circumcircle merkezini Voronoi Vertex olarak ekleyin
- Ortadaki yayı beachline'dan kaldırın
- Kaldırılan yay yüzünden geçersiz hale gelen gelecekteki circle event'leri kaldırın
- Yeni komşu olan yayların üçlülerini inceleyip circle event ekleyin
- Kuyruk boşaldığında, kalan incomplete edge'leri diyagram sınırına kadar uzatın ve sınırla kesişen noktalarda Voronoi Vertex oluşturun
Odin uygulamasındaki veri yapıları
- Örnek uygulama, C'ye alternatif bir dil olan Odin ile yazılmıştır
- Tüm kod RedPenguin101/voronoi deposunda bulunur
- Temel türler:
V2:[2]intbiçiminde bir 2D noktaPointPair: ikiV2'den oluşan çiftEvent:{site: bool, a, b, c: V2}yapısı
Event'in anlamı türüne göre değişir- site event'te
a, site koordinatıdır;bveckullanılmaz - circle event'te
a,b,c, event'i oluşturan beachline üzerindeki üç yaydır
- site event'te
Fortuneyapısı şu durumu yönetirbeachline:V2dizisiqueue:Eventdizisiincomplete_edges:PointPair -> V2haritasıvd: Voronoi diyagramını saklayan DCEL
Uygulamada atlanan veya basitleştirilen kısımlar
- beachline bir vektör olarak gösterilmiştir, ancak verimlilik için binary tree daha uygundur
- event queue da kavramsal olarak bir priority queue'dur, ancak örnek uygulamada diziye sıralı ekleme yöntemi kullanılır
- circle event geçersizleştirme işlemi, gelecekteki event'leri dolaşıp kontrol ederek yapılır; daha hızlı bir yönteme ihtiyaç olduğuna dair bir TODO bulunur
clean_beachline_edges, beachline'ın iki ucundaki gereksiz yayları kesip atma prosedürüdür- Uygulama; aynı x koordinatına sahip site'lar, circle point ile site'ın çakıştığı durumlar ve referans noktası çakışmaları gibi istisna işlemlerini içerir
- Kuyruk boşaldıktan sonra kalan incomplete edge'leri, twin'i olmayan half edge'leri ve vertex'leri temizleyen son adım yalnızca basit matematik işlemleri olarak ele alınır
DCEL ile Voronoi diyagramını saklamak
- Voronoi diyagramı genellikle Doubly Connected Edge List (DCEL) ile saklanır
- DCEL, vertex ve edge'lerden oluşan bir cell-complex'i işlemeyi kolaylaştıran bir veri yapısıdır
- Edge merkezli bir gösterimdir, ancak vertex ve face bilgilerini de birlikte tutar
- Normal bir edge'in yönü yoktur, fakat DCEL'de her edge iki yönlü iki half edge olarak saklanır
- Voronoi diyagramında DCEL'e kaydedilen vertex'ler site değil, Voronoi Vertex'lerdir
Eedge'inin hedefiE.twin.originile, sağdaki face iseE.twin.leftile elde edilir
1 yorum
Hacker News yorumları
Bir süre önce ClojureScript ile Fortune algoritmasının ilerleyişini animasyonlu gösteren bir uygulama yapmıştım: https://voronoi.ajwerner.net/#/app-diagrams
Gerçekten çok güzel bir algoritma
Ancak o projeden sonra Fortune algoritmasından biraz soğudum; çünkü kayan nokta sayısal kararlılığı iyi değil
Noktalar aynı doğru üzerindeyse ya da kayan nokta açısından neredeyse aynı doğru üzerindeymiş gibi yakınsa bozulabiliyor
Yanlış hatırlamıyorsam bu açıdan delaunator daha iyi: https://github.com/mapbox/delaunator
Referans sayfasında “old” uygulamasına bir bağlantı görünüyor; mevcut animasyon sürümünü de açık kaynak olarak yayımlama ihtimali var mı merak ediyorum
Birkaç yıl önce böyle bir 3D görselleştirme yapmıştım: https://x.com/KangarooPhysics/status/1253336959755251716
uBlock Origin ile tanınan Raymond Hill’in bir JavaScript uygulaması var: https://github.com/gorhill/Javascript-Voronoi
Burada biraz kurcalayıp hareketli hale getirdim: https://animations.adgent.com/voronoi.html
Bir videoyu girdi olarak alıp Voronoi biçiminde gösteren bir algoritmaya vermek mümkün mü merak ediyorum
O noktada teknik olarak Voronoi diyagramı olmayabilir ama oldukça havalı görünebilir
D3.js’te yeni bir uygulama var: https://github.com/d3/d3-delaunay
O sayfanın alt tarafında kullanılan süpürme algoritmasının açıklaması ve JavaScript dışındaki dillerdeki diğer uygulamaların listesi var
Eski d3-voronoi kullanımdan kaldırılacak, ama buradan görülebilir: https://github.com/d3/d3-voronoi
Kenarlarla ilgilenmiyor, yalnızca her noktayı farklı bir renkle boyamak istiyorsanız, tohum noktalarından başlayan bir flood fill varyasyonu kullanabilirsiniz
Bir pikseli yalnızca o rengin mesafesi, pikselde hâlihazırda boyanmış renkten daha kısa olduğunda yığına eklemek yeterli
Tepelerin üstünden 2D ortografik projeksiyonla render ederseniz z-buffer, en yakın tepe noktasının pikselini korur
Bunu shader ile yapmanın bir yolu da vardır, ama klasik 3D koni demosunu anlamak ve uygulamak çok kolaydır
D3’ün Fortune algoritmasından https://mapbox.github.io/delaunator/’a geçmiş olması ilginç
Gerekçe şu: “Delaunay üçgenlemesi veya Voronoi diyagramları oluştururken d3-voronoi’den 5–10 kat daha hızlı, sayısal olarak daha sağlam, yerleşik Canvas render desteğine sahip ve Delaunay grafı üzerinde gezinme ile çeşitli iyileştirmeler sunuyor”
Şu an döşemeleri hesaplayan kod acı verecek kadar saf
Yeni tartışma: https://github.com/KaliedaRik/Scrawl-canvas/discussions/120
Bu yazı Steve’in bu aralar nerede olduğunu aramama neden oldu
Onu onlarca yıl önce tanıyordum
Konuyla ilgili okunmaya değer bir yazı: https://news.ycombinator.com/item?id=37998923 - Fortune algoritmasıyla O(n log n) sürede Voronoi diyagramları ve Delaunay üçgenlemesi oluşturmak (2020)
Önceki yazı ve tartışmada diğer algoritmaların kısa özetleri de var
Kişisel olarak hâlâ en çok Jump Flooding Algorithm hoşuma gidiyor: https://en.wikipedia.org/wiki/Jump_flooding_algorithm