g3Sharp ile Ağ Örgüsü Oluşturma ve Mekansal Sorgu
ORİJİNAL YAZI: http://www.gradientspace.com/tutorials/2017/7/20/basic-mesh-creation-with-g3sharp
(Not: Yazıyı plaza diliyle yazmak istemedim. Terimlerin Türkçe karşılıklarını kullandım. Terimlerin İngilizcelerine daha aşinaysanız en aşağıdaki terimler sözlüğüne göz atın.)
(Not2: Çeviri, doğruluk açısından orijinal yazarın ağzından, birinci şahıs ifadesi kullanılarak yapılmıştır.)
Bu ilk girdide, geometry3SharpDemos projesinde Issue # 2 kısmında bir kullanıcı tarafından gönderilen soruyu yanıtlayacağım. Özetlemek gerekirse kullanıcı geometry3Sharp kütüphanesini kullanarak ağ örgüsü oluşturma ve ağ örgüsü yüzeyine bir takım ışın-atım işlemleri yapmak istiyor.
İlk sorun, x/y/z nokta koordinatı, üçgen indisi ve de yüzey normali (bu kısım kullanıcının sorusuyla alakalı olmayabilir) listelerinden DMesh3 nesnesi oluşturmak. Bunu yapmak çok da zor değil ancak kendim kodlama yaparken o kadar çok karşıma çıkıyor ki bu nesne oluşturma işlemini tek satırda halledecek yardımcı bir fonksiyon eklemeye karar verdim:
DMesh3 mesh = DMesh3Builder.Build(vertices, triangles, normals)
DMesh3Builder.Build() fonksiyonu C# jenerikleri kullanarak yazıldı. Arka planda yaptığı işlem, girdi arabelleklerinin hangi türde olduklarını anlamak için bir tip sorgusu yapmak ve doğru türlere çevirmek. Yani, vertices ve normals argümanları float[] dizisi, List<Vector3f> listesi ya da herhangi diğer <float>, <double>, <Vector3f>, <Vector3d> türünde bir IEnumerable olabilir. Aynı şekilde triangles argümanı bir int[] dizisi ya da herhangi diğer bir IEnumerable<int> veya <Index3i> türünde olabilir.
Bu çatı fonksiyon en verimli yöntem olmayabilir. Arkaplanda yaptığı işlem ise şu:
DMesh3 mesh = new DMesh3(MeshComponents.VertexNormals);
for ( int i = 0; i < NumVertices; ++i )
mesh.AppendVertex(new NewVertexInfo(vertices[i], normals[i]));
foreach ( Index3i tri in triangles )
mesh.AppendTriangle(tri);
NewVertexInfo türü, nokta rengi ve UV eşlemleri gibi diğer durumlar için ek nesne oluşturucular içeriyor. Aklınızda bulunması gereken, renk ekleme işlemi yapmadan önce bu dahili veri yapıları için bellekte yer ayırmanız gerekiyor. Bunun için de DMesh3.EnableVertexColors() gibi fonksiyonlar kullanabilir ya da nesne oluşturucunun içinde bit mantığı düzeyinde veyalama ( | ) işlemiyle ek işaretler ekleyebilirsiniz (ör. MeshComponents.VertexColors).
Bu şekilde bir ağ örgüsü oluşturduktan sonra, dahili veri yapılarının yerli yerinde olduğunu kontrol etmek iyi bir karar olacaktır. Eğer sorun varsa, bazı durumlarda AppendTriangle() fonksiyonu kuraldışılık hatası verecektir ama nesne oluşturma fonksiyonu içerisinde ağ örgüsünün düzgün bir şekilde yapılandırıldığını enine boyuna kontrol etmiyoruz çünkü bu kontroller performans yönünden bize pahalıya mal olacaktır. Bunun yerine, DMesh3.CheckValidity() fonksiyonunu çağırabilirsiniz. Bu fonksiyon parametre olarak bir FailMode argümanı alır ve bir sorun bulunduğunda bu argümana göre kuraldışılık hatası vereceğine, belirtme yapacağına ya da false değeri döndüreceğine karar verir.
(Eğer sorunlar bulursanız, bunları düzeltmek zor olabilir. Şimdilik Autodesk Meshmixer kullanmanızı tavsiye ediyorum.)
Ağ Örgüsü için Temel Dosya Girdi-Çıktısı
Yukarıda bahsedildiği gibi ağ örgünüzü oluşturduktan sonra, neye benzediğini görmek isteyebilirsiniz. Bunu, ağ örgünüzü diske aktardıktan sonra önceden bahsettiğim Meshmixer gibi bir görüntüleyicide açarak yapabilirsiniz. Tekli ağ örgüsünü, azıcık çarpık da olsa tek satırlık kod diyebileceğim bu kod parçasıyla çıkartabilirsiniz:
IOWriteResult result = StandardMeshWriter.WriteFile(path,
new List<WriteMesh>() { new WriteMesh(mesh) }, WriteOptions.Defaults);
Desteklenen dosya türleri: OBJ, STL ve OFF. STL için varsayılan ASCII formatıdır ancak daha küçük, ikili yapıda bir STL dosyası istiyorsanız bunu WriteOptions veri yapısını düzenleyerek yapabilirsiniz. WriteOptions, standard ve formata özgü birçok seçenek barındırır.
Ağ örgüsünü diskten okumak isterseniz, StandardMeshReader sınıfını kullanabilirsiniz. Şu anda sadece OBJ, STL ve OFF formatlarını okuyabilir. MeshFormatReader arayüzünü kullanarak kendiniz de yeni format okuyucular ekleyebilirsiniz. Bir ağ örgüsünü okumanın en kısa yolu şu tek satırlık koddur:
DMesh3 mesh = StandardMeshReader.ReadMesh(path)
Bu kod birçok durumda çalışır ancak dosyanızda birden çok ağ örgüsü varsa, hata geri bildirimi almak istiyorsanız ya da okuma seçeneklerini düzenlemek istiyorsanız, ayrıntılı yöntemi kullanmanız gerekir:
DMesh3Builder builder = new DMesh3Builder();
StandardMeshReader reader = new StandardMeshReader() { MeshBuilder = builder };
IOReadResult result = reader.Read(path, ReadOptions.Defaults);
if (result.code == IOCode.Ok)
List<DMesh3> meshes = builder.Meshes;
OBJ formatı için materyalleri de okuyabilirsiniz ama doku dosyalarını kendiniz yüklemeniz gerek. Bu biraz karmaşık bir işlem, o yüzden belki gelecek yazılardan birinde anlatırım.
Mekansal Veri Yapıları Sorgusu
Kullanıcı tarafından sorulan sorunun sonraki kısmı verimli bir şekilde ışın-kesişim sorguları yapmak için mekansal bir veri yapısı oluşturmak üzerine. g3Sharp şu anda sadece Eksen Hizalı Sınırlayıcı Kutu ağaçlarını destekliyor. Bu yapıyı oluşturmak için şu iki satır yeterli:
DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh);
spatial.Build();
Eğer ağ örgüsü büyükse bu işlem birkaç saniye alabilir ancak buna değecektir çünkü ortaya birçok sorgu imkanı sunan mekansal bir veri yapısı çıkacak. Örneğin, şu şekilde bir ışın-atım hesaplaması yapabiliriz:
Ray3d ray = new Ray3d(origin, direction);
int hit_tid = spatial.FindNearestHitTriangle(ray);
Tabii ki gönderdiğimiz ışın hedefi ıskalayabilir. Bu yüzden elde edilen üçgenin kimliğini kontrol etmemiz gerek:
if (hit_tid != DMesh3.InvalidID) {
IntrRay3Triangle3 intr = MeshQueries.TriangleIntersection(mesh, hit_tid, ray);
double hit_dist = origin.Distance(ray.PointAt(intr.RayParameter));
}
Yapılan sorgu bir nokta, üçgen ya da kenar indisi döndürdüğünde, genellikle elde edilen dönütü DMesh3.InvalidID değerine karşı kontrol etmeniz gerekir. Bu sayede sorgunun gerçekten bir şey bulup bulmadığından emin olursunuz.
DMeshAABBTree3 aynı zamanda, birçok uygulamada çok yararlı bir araç olan en-yakın-nokta sorgularını da destekler. İşte bir girdi noktasını baz alarak ağ örgüsündeki en yakın noktayı bulan standard kod:
int near_tid = spatial.FindNearestTriangle(point);
if (near_tid != DMesh3.InvalidID ) {
DistPoint3Triangle3 dist = MeshQueries.TriangleDistance(mesh, near_tid, point);
Vector3d nearest_pt = dist.TriangleClosest;
}
Bunlar benim en çok kullandığım sorgular. Ancak tabii ki başkaları da var. Örneğin, FindAllHitTriangles(Ray3d) bütün ışın-üçgen kesişimlerini bulur ya da TestIntersection(Triangle3d) bir üçgenin ağ örgüsüyle kesişimini test eder.
DMeshAABBTree3, aynı zamanda IsInside(point) fonksiyonunu kullanarak bir noktanın içeride ya da dışarıda olma durumunu sorgulamayı da destekler. Bu fonksiyon sadece ağ örgüsü kapalıysa düzgün çalışır. Ancak belirtmek gerekirse, şu anki algoritma çok da verimli değil (FindAllHitTriangles() fonksiyonunu kullanıp geçişleri sayıyor).
İki ağ örgüsü arasındaki kesişimleri kontrol etmek için TestIntersection(DMeshAABBTree3 otherTree) fonksiyonunu kullanabilirsiniz. Bu yöntem üçgenleri tek tek test etmekten daha verimli çünkü sınırlayıcı-kutu hiyerarşilerini özyinelemeli olarak azaltıyor. Bu fonksiyon aynı zamanda, gerçekten nokta konumlarını değiştirmeden, ikinci ağ örgüsüne dönüşüm uygulamayı mümkün kılan, isteğe bağlı bir argüman alıyor: Func<Vector3d, Vector3d> TransformF. Ağ örgüleriniz aynı koordinat sistemi üzerindeyse bu argümana null değeri atayabilirsiniz. Amma ve lakin, diyelim ki, Unity içerisinde, üzerinde hiyerarşik dönüşümler olan iki ağ örgüsü arasındaki kesişimleri hesaplamaya çalışıyorsunuz. Bu durumda, ağ örgülerinden birini diğerinin uzayına eşleyen, uygun bir dönüşüm fonksiyonu atamak, kodunu sadeleştirecektir.
Son olarak, DMeshAABBTree3 mekansal ayrışımından yararlanarak kendi mekansal sorgularınızı yazmak isterseniz, TreeTraversal sınıfını kullanabilirsiniz. Tek yapmanız gereken kutu ve üçgen test fonksiyonlarını değiştirmek ve kendi nesne oluşumunuzu DoTraversal() fonksiyonuna argüman olarak vermek. Bu sistemin nasıl çalıştığıyla ilgili daha fazla bilgi için kod yorumlarına göz atın.
Eveeeet. Var olan ağ örgüsünden DMesh3 yüklemeyi öğrendin, sıfırdan kendin bir tane oluşturmayı öğrendin, bundan AABB ağacı çıkarmayı öğrendin, ışın-atım ve en-yakın-nokta hesaplamayı falan da öğrendin. Bu kadarı bile yordamsal geometri üretimi konusunda fantastik işler yapman için yeterli.
Terimler Sözlüğü
| Ağ örgüsü | Mesh |
| Nokta | Vertex |
| Işın | Ray |
| Işın-atım | Ray-cast |
| Nesne | Object |
| Girdi/çıktı | Input/output |
| Arabellek | Buffer |
| Sorgu | Query |
| Bellekte yer ayırma | Allocate |
| Eşlem | Mapping |
| Nesne oluşturucu | Constructor |
| Veri yapısı | Data structure |
| Kuraldışılık hatası | Exception |
| Nesne oluşumu | Instance |
| Mekansal | Spatial |
| Doku | Texture |
| Eksen Hizalı Sınırlayıcı Kutu | Axis Aligned Bounding Box |
| Yordamsal | Procedural |
| Özyinelemeli | Recursively |
| Dönüşüm | Transformation |

Son Yorumlar