WPF - 2D, 3D ve Yüzey (Topoloji) Uygulamaları

Windows Presentation Foundation ile yapılmış profesyonel projeleri incelediğimizde genellikle 3D işlemlerinin uygulandığını görüyoruz. Geliştiriciler uygulamalarında 3D ‘yi tercih etmelerinin sebeplerinin başında WPF ‘in grafik kartlarını başarılı bir şekilde kullanabilmesi gelmektedir. Bu başarıyı sağlayan en büyük etmen ise Windows Vista işletim sistemli bilgisayarlarda grafik kartını çok daha performanslı bir şekilde kullanabilmesidir. Vista işletim sisteminde animasyonların bir çoğunun WPF ‘te kullanılan sınıflar ile yapıldığını düşünürsek neden 3D işlemlere daha fazla yer verildiğini anlayabiliriz.

Günümüzde hazırlanan ve hazırlanmakta olan uygulamaları göz önüne alarak bizlerde uygulamalarımızda 3D işlemleri uygulamamız, yeni teknolojileri yakalamak açısından yararlı olacaktır.

Bu ve diğer bir çok sebebi düşünerek bu makalemizde Windows Presentation Foundation ile 3D uygulamalarını nasıl geliştireceğimizi ayrıntılı bir biçimde incelemeye çalışacağız.

Daha önceleri ayrıntılı bir 3D uygulaması geliştirmek istiyorsak yönetilebilir 3D programlama yapısını, vektörsel 2D programlama yapısını bilmemiz gerekiyordu. Tabii bu bilgileri bilmememiz durumunda uzunca bir süre bunları öğrenmek ile zaman kaybettikten sonra uygulamalarımızı geliştirmeye başlayabiliyorduk. Fakat çok daha ileri düzey uygulama geliştirmek istediğimizde harcayacağımız zaman aralığı oldukça artacak ve belki de hazırlayacak olduğumuz uygulama sonlandığında çalıştığımız sistemler güncelliğini kaybedecektir.

Fakat .net 3.0 yapısı ile bizlere sağlanan kolaylıkları kullanarak biraz önce bahsettiğimiz o zorlu 3D uygulamalarını rahatlıkla yapabilmemize olanak tanınmaktadır. Biz bu makalemizde 3D işlemlerimizi bir tutorial gibi en temelden ele alarak eğitsel bir biçimde inceleyeceğiz.

3D Grafik Teorisi

WPF ile 3D programlamaya başlarken nasıl yapacağı konusunda herhangi bir fikri olmayanlar için açıklamaya başlarsak, 3D modelleme ve çizim işlemlerini yaparken Mesh (ağ topluluğu) içerisine noktalarımızı yerleştirmeli, üçgen (triangle) ve doğrusal (normal) çizimler ile de istediğimiz şekli çizmeliyiz. Bu karmaşık gelen işlemleri çok az 3D modelleme bilgisi ile WPF ‘de çabucak işlemlerimizi yapabilmemiz mümkündür. Bu işlemleri yapmaya başlarken biraz önce saydığımız üç özelliği incelemek çok daha yararlı olacaktır.

Mesh Nedir?

Mesh, yüzeyi yeniden göstermek olarak kısaca tanımlayabiliriz. Çizimlerimizi yaparken köşe noktalarımızı belirler, en yüksek ve en alçak yükseltilerini belirleyerek basitçe çizim yapabiliriz.

Peki, “biz daha önceden mesh ‘i kullanmak isteseydik kullanamaz mıydık?” diye bir soru kafanıza takılırsa bunun cevabı evet olurdu. .net Framework 2.0 dan beri mobile uygulamalarda DirectX kütüphanesinde Direct3D nesnesini kullanabilmemiz mümkündür. Eğer ki mobile uygulamalarınızda mesh sınıfını kullanmayı düşünüyorsanız bu sınıf .net 3.5 ile de yapıda yer almıştır.

Mesh ‘i WPF üzerinden incelemeye devam edelim. Yüzey yüksekliğini minimum olarak belirlersek bizim karşımıza yalnızca düz bir yüzey çıkacaktır. Bu yüzeyde en basit anlamda bir üçgene benzer fakat yükseklikleri ve farklı koordinat noktalarını belirttiğimiz zamanda karşımıza küp görünümlü bir ekran görüntüsü alabiliriz. Biraz daha farklı düşünürsek, tek bir yükseklik değil de birden fazla düşünmemiz durumunda ise tümsekli bir yeryüzü yapısı elde edebilmemiz mümkündür.

Mesh ile çizim işlemlerimiz aşağıdaki üçlü sayesinde oluşturulabilmektedir.
Mesh pozisyonu
Triangle Indices
Üçgen standartları

Mesh Pozisyonu

Mesh pozisyonu, çalışacağımız yüzeyde belirlediğimiz noktalar sayesinde hangi alanda çalışacağımızı belirlememize yarar.

Triangle Indices

Mesh ile çalışırken temel olarak 3 nokta üzerinde çalışıp üçgen ‘e benzer çizim işlemleri yapabilmemiz mümkündür. Fakat biz bu üç noktadan daha fazla çalışmak istersek farklı köşegen noktaları belirlememiz gerekecektir.

WPF ile çalışırken ise bizden mesh ‘imizin koordinat noktalarını girmemiz istenir. Bizde bu noktaları diziye benzer bir şekilde uygulamamıza ekleriz. Örneğin, beş tane koordinat noktası eklemek istediğimizde {n0,n1,n2,n3,n4} biçiminde ekleyebiliriz. Burada n0..5 ile belirlediğimiz koordinatlar uygulamada point2d veya point3d koordinat noktalarına karşılık gelecek olan noktalardır. Eğer biz bu girdiğimiz koordinat noktaları ile bir üçgen çizmek istiyorsak bu noktalardan üçünü kullanmamız yeterli olacaktır veya koordinat noktalarına girdiğimiz değerler diziye girdiğimiz sırayla değil, bu durumda ise bu koordinat noktalarını istediğimiz şekilde değiştirebilmemiz mümkündür. {n2,n1,n4,n0,n3} Daha sonra biz bu noktaları uygulamamızda çağırırken 4. nokta diye belirteceğiz. Biz kod içerisinde 4. nokta dediğimizde o n0 ‘ı algılayacaktır. Aynı şekilde diğer noktaları da bu şekilde çağırabilmemiz mümkündür.

Biraz önce bahsettiğimiz koordinat noktalarının uygulamada çağırılma işlemi esnasında bizler bu noktaların içerisine girdiğimiz değerleri dışarıdan da alarak şekillerimizin X, Y, Z koordinatlarında şekillenmesine olanak tanıyabiliriz.

Ayrıca doğru çizimlerini yaparken unutmamamız gereken bir önemli noktada bu doğruları uygulamada belirtirken saat yönünün tersinde belirtmemiz gerektiğidir. Yani bunu kısaca örneklemek gerekirse, sağ elini kullananlar kâğıt üzerinde çizim yaparken ilk olarak en sağdaki noktayı çizer ve daha sonra sola doğru çizim yaparlar. İşte WPF ‘de çizim işlemlerini yaparken bu mantıkla çalışmaktadır. Yani bizler WPF ile çizim yaparken ilk olarak en sağ noktasını belirtip daha sonra en sol noktaya kadar olan noktaları belirterek işlemlerimizi yapabilmemiz mümkündür.

Peki, neden sağ el ile çizim örneğini verdik. Hemen isterseniz elinize bir kâğıt ve kalem alıp deneyin (Örneğimiz sağ elini kullananlar içindir). Bir sağdan sola, birde sağdan sola doğru çizim yapmayı deneyin. Dikkat edeceksiniz ki sağdan sola doğru yaptığınız çizim diğer koordinatlarda yaptığınız çizim işlemlerine göre çok daha düzgün olacaktır. Dünyada ki insanların büyük bir çoğunluğu da sağ ellerini kullandıkları için bilinçaltında kabul edilmiş çizim mantığının dışına çıkılmaması için böyle bir düşünce içerisine girilmiş.

Üçgen Standartları

Hepimizin ilkokuldan bildiği bir bilgi üçgenler üç noktadan oluşur. Bu üç noktaları bilgisayar ortamında çizmeye çalıştığımızda üç noktaya karşılık 6 koordinat noktaları verir ve çizimlerimizi yapardık. Daha önceden bilgisayar grafikleri ile uğraşamamış olan arkadaşlar için neden altı koordinat noktası girilmesi gerektiğinden kısaca bahsedeyim. Bilindiği üzere uygulamamızın formunun üzerinden işaretlenen bir noktanın ayrıntılarını incelediğimizde X ve Y noktalarından oluştuğunu ve bu bilgilerin tutulduğu gözlenir. Bizlerde üçgen oluşturmak istediğimizde A noktası için (X1 ve Y1), B noktası için (X2 ve Y2) ve son olarak ta C noktası içinde (X3 ve Y3) koordinatlarının bilgilerini tutar. Son olarak ta bu üç noktayı üçgen oluşturma kurallarına aykırı olmadığı takdirde birleştirdiğimiz zaman bir üçgen oluşacaktır.

Çizim işlemlerimizi yaparken birden fazla yöntem izleyebiliriz. Bunlar ABxAC, BCxBA veya CBxCA noktalarını ele alarak çizim işlemlerimizi yapabiliriz.




Üç basit doğru ile bir üçgen çizebildik. Bu noktaları arttırıp, açı değerlerini aralarında 90 derece olacak şekilde ayarlayıp koordinat noktalarını belirmemiz durumunda ise küp çizebilmemiz mümkündür.

Buraya kadar çizimin nasıl yapılacağına değinmeye çalıştık. Şimdi bu edindiğimiz bilgilerin daha kalıcı olabilmesi için örnek uygulamalar yapacağız.

Uygulama Geliştirmeye Başlarken

Yapacağımız örneğin daha anlaşılır olabilmesi için ilk başta bir kullanıcı arayüzü hazırlayacağız. Hem de uygulamamızı kullanacak olan son kullanıcılar için daha hoş bir görünüm sunulacaktır. Hazırladığımız kullanıcı arayüzünde 3D nesnelerin gözükebilmesi için viewport3D kontrolü kullanılmıştır.

<Grid>
<DockPanel
Width="Auto"
VerticalAlignment="Stretch"
Height="Auto"
HorizontalAlignment="Stretch"
Grid.ColumnSpan="1"
Grid.Column="0"
Grid.Row="0"
Margin="0,0,0,0"
Grid.RowSpan="1">
<StackPanel>
<StackPanel.Background>
<LinearGradientBrush>
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="DarkGoldenrod" Offset=".3"/>
<GradientStop Color="DarkGoldenrod" Offset=".7"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</StackPanel.Background>
<StackPanel Margin="10">
<Button
Name="buton"
Click="butonClick">Ucgen</Button>
</StackPanel>
</StackPanel>
<Viewport3D Name="anaGoruntuleme" ClipToBounds="True">
<Viewport3D.Camera>
<PerspectiveCamera
FarPlaneDistance="100"
LookDirection="-11,-10,-9"
UpDirection="0,1,0"
NearPlaneDistance="1"
Position="11,10,9"
FieldOfView="70" />
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight
Color="White"
Direction="-2,-3,-1" />
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</DockPanel>
</Grid>

3D alanımızı basit bir biçimde Viewport3D kontrollerini kullanarak oluşturuyoruz. Viewport3D kontrollerini kullanmamızın sebebi üç boyut işlemlerini çok daha performanslı gerçekleştirebilmesinden ötürüdür. Viewport3D kontrolünün özelliklerini MSDN ‘de incelerseniz size not olarak “eğer 3D işlemlerini kullanacaksanız Viewport3D kontrolünün içerisinden kullanmanız uygulamanızın performansı açısından daha yararlı olacaktır” ile karşılaşacağız. Normalde form kontrolleri 2D işlemlerini çok performanslı gerçekleştirirken 3D işlemlerinde zaman kaybına uğradığı bilinmektedir. Bu zaman kaybının önlemek içinde Viewport3D kontrolünün kullanılması daha mantıklıdır.

Ayrıca daha sonradan kullanıcılara görüntümüzü farklı açılar üzerinden göstermeyi amaçladığımız için PerspectiveCamera özelliğini ekliyoruz. PerspectiveCamera Viewport3D kontrollünün alt özelliklerinden biridir. Varsayılan olarak kamera açısı {0, 0, 0} olarak bizlere sunulmaktadır.

DirectionalLight, yine Viewport3D kontrollerinden birisidir. Bu özellik uygulamamızdaki nesnemize belirli bir açı ile ışık geliyormuş hissi verir. Sanki gerçek hayatta nesnelerin üzerine güneş ışığının çarpması sonrasında belirli bir parlaklık alması gibi algılayabiliriz.

Hazırlamış olduğumuz kullanıcı kontrolü aşağıdaki gibi oluşmuştur.



Daha önceki WPF makalelerimizi incelediyseniz uygulamalarımızı genellikle XAML kod tarafında hazırlardık. Bu sefer ise C# kod tarafında da kod yazacağız. Bu sayede uygulamamızda yönetilebilir kodda kullanılmış olacak.

Kod tarafına geçtiğimizde isteklerimize karşılık verilebilmesi için butonun tıklanması olayına dayanarak işlemlerimizi yapacağız. Kullanıcı arayüzümüzü tasarlarken bir de ekrana buton eklemiştik. Bu eklediğimizi butonun name özelliğine istediğimiz ismi verebilmemiz mümkündür. Ben bu yazımız için buton veriyorum. Butona ismi verdikten sonra tıklanması sonrasında olayların gerçekleştirilebilmesi için kod tarafında butonu belirtmemiz gerekmektedir. Bu işlem esnasında ister XAML ile oluşturduğumuz butonun üstüne çift tıklar otomatik oluşturturuz ya da kendimiz nesne özellikleri ve yönlendirmesini belirterek biz oluştururuz.

İşlemi gerçekleştirdiğimizde karşımıza aşağıdaki bir kod oluşmalıdır.

private void butonClick(object sender, RoutedEventArgs e)
{
}

Basit Bir Çizim Modeli Oluşturulması

Viewport3D kontrollünün içerisinde kullanabileceğimiz tipleri ve 3D sınıfları kısaca tanımlayalım.

GeometryModel3D: .net3.0 ile gelmiştir. Mesh (MeshGeometry3D) ile 3 boyutlu çizim nesneler oluşturabilmemize olanak tanır.
MeshGeometry3D: .net3.0 ile gelmiştir. Bünyesinde nesnemizin pozisyonlarını, koordinatlarını ve normal çizgileri tutar.
Point3D: .net3.0 ile gelmiştir. Çizeceğimiz nesnelerin koordinat noktalarını tutar.
Vector3D: .net3.0 ile gelmiştir. Normal çizgileri oluşturur.
DiffuseMetarial: .net3.0 ile gelmiştir. Oluşturduğumuz modele renginin ve üzerinde tasarım var ise o özelliklerini vermemize olanak tanır.
DirectionalLight: .net3.0 ile gelmiştir. Nesnemizi aydınlatabilmemize olanak tanır.
Ayrıca bu özellikleri kullanabilmemize olanak tanıyan isim alanı System.Windows.Media.Media3D ‘dır. Bu sınıfa ait özellikleri yeri geldikçe inceleyeceğiz.

Uygulamamıza Kod Ekliyoruz

İlk olarak biraz önce bahsettiğimiz tipleri, özellikleri ve daha fazlasını kullanabilmemiz için Media3D sınıfını uygulamamıza ekliyoruz.

using System.Windows.Media.Media3D;

Şimdi ise üçgen çizebilmemiz için gerekli olan kodları eklemeyelim.

İlk olarak yeni bir MeshGeometry3D oluşturalım;
MeshGeometry3D ucgenYuzey = new MeshGeometry3D();

Sıradaki adımımız üçgenimizi oluşturabilmek için üç nokta eklememiz gerekmektedir.

Point3D nokta0 = new Point3D(0, 0, 0);
Point3D nokta1 = new Point3D(5, 0, 0);
Point3D nokta2 = new Point3D(0, 0, 5);

Şimdi eklediğimiz bu üç noktayı üçgen oluşturabilmesi için ucgenYuzey ’e ekliyoruz.

ucgenYuzey.Positions.Add(nokta0);
ucgenYuzey.Positions.Add(nokta1);
ucgenYuzey.Positions.Add(nokta2);

Şimdi üçgenimizin çizilmesi için Triangle Indices ‘e noktaları tanımlıyoruz. Bu işlemleri yaparken de çizimleri sağ elimizle yaptığımızı anlatan örneği aklımıza getirmek yararlı olacaktır.

ucgenYuzey.TriangleIndices.Add(0);
ucgenYuzey.TriangleIndices.Add(2);
ucgenYuzey.TriangleIndices.Add(1);

Doğrularımızın çizilebilmesi için noktalarımızı Vector3D ‘ye aktarıyoruz ve doğru olarak çizmesini belirtiyoruz.

Vector3D normal = new Vector3D(0,1,0);
ucgenYuzey.Normals.Add(normal);
ucgenYuzey.Normals.Add(normal);
ucgenYuzey.Normals.Add(normal);

Son olarak üçgenimizin yüzeyini ve oluşturulacak olan modele ait bilgileri nereden alacağını belirtmemiz gerekmektedir.

Material madde = new DiffuseMaterial(new SolidColorBrush(Colors.DarkGoldenrod));
GeometryModel3D ucgenModel = new GeometryModel3D(ucgenYuzey, madde);
ModelVisual3D model = new ModelVisual3D();
model.Content = ucgenModel;
this.anaGoruntuleme.Children.Add(model);

Uygulamamızda üçgen çizilmesi için gerekli olan kodları bitirmiş oluyoruz. Bu işlemlerimizin sonucunda buton ‘unun tıklanması olayında oluşan kodlar toplu halde aşağıdaki gibi olmuştur.

private void butonClick(object sender, RoutedEventArgs e)
{
MeshGeometry3D ucgenYuzey = new MeshGeometry3D();
Point3D nokta0 = new Point3D(0, 0, 0);
Point3D nokta1 = new Point3D(5, 0, 0);
Point3D nokta2 = new Point3D(0, 0, 5);
ucgenYuzey.Positions.Add(nokta0);
ucgenYuzey.Positions.Add(nokta1);
ucgenYuzey.Positions.Add(nokta2);
ucgenYuzey.TriangleIndices.Add(0);
ucgenYuzey.TriangleIndices.Add(2);
ucgenYuzey.TriangleIndices.Add(1);
Vector3D normal = new Vector3D(0,1,0);
ucgenYuzey.Normals.Add(normal);
ucgenYuzey.Normals.Add(normal);
ucgenYuzey.Normals.Add(normal);
Material madde = new DiffuseMaterial(new SolidColorBrush(Colors.DarkGoldenrod));
GeometryModel3D ucgenModel = new GeometryModel3D(ucgenYuzey, madde);
ModelVisual3D model = new ModelVisual3D();
model.Content = ucgenModel;
this.anaGoruntuleme.Children.Add(model);
}

Bu işlemlerimiz sonucunda oluşan ekran görüntüsü aşağıdaki gibi oluşmuştur.


Evet, bu şekilde üçgeni çizebilmiş oluyoruz. Şimdi ise 3 boyutlu Mesh ‘i nasıl çizeceğimize değinmeye çalışacağız.

Küp Oluşturmak

Küpü nasıl oluşturacağımızı hatırlamaya çalışalım. Küpün 12 açısı bulunmaktadır (6 yüzeyi ve iki tane kesim noktası bulunmaktadır).
Kilit noktalardan yola çıkarak saatin tersi yönünde (sağ elle çizim alışkanlığına göre) çizim yapacağımız noktaları belirleriz.

Bu iki kısa bilgiyi aklımızda tutarak uygulamamızı yapmaya devam edebiliriz.

İlk olarak yapmamız gereken küpün oluşturulması için formumuza bir buton eklenmesi gerekmektedir. Bunun için XAML kod tarafına aşağıdaki kodu ekleriz.

<Button Name="kupButon" Click="kupButonClick">Kup</Button>

Şimdi kod tarafına geçmek için butonun tıklanması olayını ekliyoruz.

private void kupButonClick(object sender, RoutedEventArgs e)
{
}

Küpümüzü çizerken üç boyutlu modelleme işlemlerimizi daha kolay yapabilmemiz için Model3DGroup sınıfını kullanacağız. Model3DGroup koleksiyonuna dahil olan GeometryModel3D nesnesi işimizi oldukça kolaylaştıracaktır. Bu yapacağımız örnek basit olarak 3D modellemeyi bize kavratacaktır.

3D Modelleme işlemlerimizi yaparken normal çizim işlemlerine ek olarak bizde iki metot ekleyeceğiz. Ekleyeceğimiz bu metotlar UcgenModeliOlustur() ve NormalHesapla() olacaktır. Şimdi bu iki metodumuzun kodlarına göz atalım.

private Model3DGroup UcgenModeliOlustur(Point3D n0, Point3D n1, Point3D n2)
{
MeshGeometry3D mesh = new MeshGeometry3D();
mesh.Positions.Add(n0);
mesh.Positions.Add(n1);
mesh.Positions.Add(n2);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
Vector3D normal = NormalHesapla(n0, n1, n2);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
Material madde = new DiffuseMaterial(
new SolidColorBrush(Colors.DarkGoldenrod));
GeometryModel3D model = new GeometryModel3D(
mesh, madde);
Model3DGroup grup = new Model3DGroup();
grop.Children.Add(model);
return grup;
}
private Vector3D NormalHesapla(Point3D n0, Point3D n1, Point3D n2)
{
Vector3D v0 = new Vector3D(
n1.X - n0.X, n1.Y - n0.Y, n1.Z - n0.Z);
Vector3D v1 = new Vector3D(
n2.X - n1.X, n2.Y - n1.Y, n2.Z - n1.Z);
return Vector3D.CrossProduct(v0, v1);
}

UcgenModeliOlustur() metodu Model3DGroup mesh ‘i kullanarak oldukça şık ve sevimli bir çizim yapmamıza olanak tanır. NormalHesapla() metodu ise UcgenModeliOlustur() metodu ile aldığımız noktalara ilişkin koordinat bilgilerinden yararlanarak bu noktaları vektöre dönüştürür. Bu iki metodun yaptığı işlemler sonrasında ise oldukça şık bir küp oluşturulmuş olur.

Tabii küpü çizebilmemiz için butonun tıklanması olayında koordinat bilgileri girmemiz gerekmektedir. Bu girdiğimiz bilgilerde UcgenModeliOlustur() metoduna oradan da NormalHesapla() metodu ile çizilmesini sağlayacaktır.


Şimdi küpümüzün butona tıklanması küpü çizebilmesi için koordinatlarına ilişkin bilgilerini kod tarafında girelim.

private void kupButonClick(object sender, RoutedEventArgs e)
{
Model3DGroup kup = new Model3DGroup();
Point3D n0 = new Point3D(0, 0, 0);
Point3D n1 = new Point3D(5, 0, 0);
Point3D n2 = new Point3D(5, 0, 5);
Point3D n3 = new Point3D(0, 0, 5);
Point3D n4 = new Point3D(0, 5, 0);
Point3D n5 = new Point3D(5, 5, 0);
Point3D n6 = new Point3D(5, 5, 5);
Point3D n7 = new Point3D(0, 5, 5);
//ön yüz
kup.Children.Add(UcgenModeliOlustur(n3, n2, n6));
kup.Children.Add(UcgenModeliOlustur(n3, n6, n7));
//sağ yüz
kup.Children.Add(UcgenModeliOlustur(n2, n1, n5));
kup.Children.Add(UcgenModeliOlustur(n2, n5, n6));
//arka yüz
kup.Children.Add(UcgenModeliOlustur(n1, n0, n4));
kup.Children.Add(UcgenModeliOlustur(n1, n4, n5));
//sol yüz
kup.Children.Add(UcgenModeliOlustur(n0, n3, n7));
kup.Children.Add(UcgenModeliOlustur(n0, n7, n4));
//üst yüz
kup.Children.Add(UcgenModeliOlustur(n7, n6, n5));
kup.Children.Add(UcgenModeliOlustur(n7, n5, n4));
//alt yüz
kup.Children.Add(UcgenModeliOlustur(n2, n3, n0));
kup.Children.Add(UcgenModeliOlustur(n2, n0, n1));
ModelVisual3D model = new ModelVisual3D();
model.Content = kup;
this.anaGoruntuleme.Children.Add(model);
}

Bu kodlarımızı uygulamamıza ekledikten sonra derlediğimizde karşımıza aşağıdaki gibi güzel bir görüntü çıkacaktır.



Viewport ‘u Temizleme

Uygulamamızda oluşturduğumuz modelleri birbirleri üzerine tekrardan oluşturmak istediğimizde dikkatimizi çeken bir sorun çıkmıştır. Üçgeni çizdirdikten sonra küpü çizdirdiğimizde bir öncekinin üstünü kapatarak küpü çizerken, küpten üçgene geçmek istediğimiz zaman çizememektedir. Bu durumun ana nedeni küpün kapladığı alanın üçgenden daha büyük olması ve çizimleri yaptırırken bir önceki sildirmeden yapmamızdan kaynaklanmaktadır. Bu kötü durumu ortadan kaldırmak için görüntü alanı olarak kullandığımız Viewport ‘u temizlememiz gerekecektir. Bu temizleme işlemi için aşağıdaki kodları metot olarak oluşturuyoruz.

private void ViewportTemizle()
{
ModelVisual3D m;
for (int i = anaGoruntuleme.Children.Count - 1; i >= 0; i--)
{
m = (ModelVisual3D)anaGoruntuleme.Children[i];
if (m.Content is DirectionalLight == false)
anaGoruntuleme.Children.Remove(m);
}
}

ViewportTemizle() metodunu başarılı bir biçimde kullanabilmemiz için küp ve üçgen çizdirmek için kullandığımız butonların tıklanması olaylarının ilk satırlarına ViewportTemizle(); metodunu çağırarak temizleme işlemini gerçekleştirebiliriz.

Kamera Kontrolleri

Biz küpü ve üçgeni basit bir şekilde oluşturduk. Fakat son kullanıcılar bizlerden bu görüntüleri farklı açılarında da görmek istiyoruz şeklinde isteklerde bulunabilirler. Bizde bu durumu yerine getirebilmek için yapmamız gereken işlemler koordinatlara ait bilgileri kullanıcıdan girmesini istemek olacaktır. Bu işlemi yerine getirebilmek için kullanıcı arayüzüne girdi girilebilmesi için TextBlock ve TextBox eklememiz gerekecektir.

<StackPanel Margin="10">
<Button Name="buton" Click="butonClick">Ucgen</Button>
<Button Name="kupButon" Click="kupButonClick">Kup</Button>
<Button Name="Kapat" Click="kapatButon">Kapat</Button>
<TextBlock Text="Kamera X pozisyonu:"/>
<TextBox Name="kameraPozisyonXTextBox" MaxLength="5"
HorizontalAlignment="Left" Text="9" Width="100" />
<TextBlock Text="Kamera Y Pozisyonu:"/>
<TextBox Name="kameraPozisyonYTextBox" MaxLength="5"
HorizontalAlignment="Left" Text="8" Width="100" />
<TextBlock Text="Kamera Z Pozisyon:"/>
<TextBox Name="kameraPozisyonZTextBox" MaxLength="5"
HorizontalAlignment="Left" Text="10" Width="100" />
<Separator/>
<TextBlock Text="Bakılan X Yönü:"/>
<TextBox Name="bakilanXTextBox" MaxLength="5"
HorizontalAlignment="Left" Text="-9" Width="100" />
<TextBlock Text="Bakılan Y Yönü:"/>
<TextBox Name="bakilanYTextBox" MaxLength="5"
HorizontalAlignment="Left" Text="-8" Width="100" />
<TextBlock Text="Bakılan Z Yönü:"/>
<TextBox Name="bakilanZTextBox" MaxLength="5"
HorizontalAlignment="Left" Text="-10" Width="100" />
<Separator/>
</StackPanel>

XAML kod tarafımız yukarıdaki gibi olmuştur. Şimdi yapmamız gereken işlem ise TextBox lardan alınan bilgiler ile 3D modelimizin kamera açılarının değişmesidir. Bunu yapabilmek için KameraAiınan() isimli bir metot oluşturuyoruz.

private void KameraAlinan()
{
PerspectiveCamera kamera = (PerspectiveCamera)anaGoruntuleme.Camera;
Point3D pozisyon = new Point3D(
Convert.ToDouble(kameraPozisyonXTextBox.Text),
Convert.ToDouble(kameraPozisyonYTextBox.Text),
Convert.ToDouble(kameraPozisyonZTextBox.Text)
);
Vector3D lookDirection = new Vector3D(
Convert.ToDouble(bakilanXTextBox.Text),
Convert.ToDouble(bakilanYTextBox.Text),
Convert.ToDouble(bakilanZTextBox.Text)
);
kamera.Position = pozisyon;
kamera.LookDirection = lookDirection;
}

Uygulamalarımızda da kamera kontrollerinin işleyebilmesi için KameraAlinan() metodunu butonların tıklanması olayına eklememiz yeterli olacaktır. Metodu çağırma işlemini tamamladıktan sonra uygulamamızı çalıştırdığımızda aşağıdaki gibi bir görüntü elde ederiz.

ScreenSpaceLines3D Sınıfı

Bahsettiğimiz bu sınıf .net3.0 yapısının içerisinde bulunmamaktadır. Sınıfı kullanabilmemiz için yapmamız gereken CodePlex 3D Tools ‘te geliştiriciler tarafından geliştirilmekte olan 3DTools uygulaması indirerek uygulamamıza referans olarak eklememiz yeterli olacaktır.



Bu dll ‘i referans olarak uygulamamıza ekleriz.

using _3DTools;

Artık ScreenSpaceLines3D sınıfını kullanabiliriz.

Modelde Köşe Noktalarına ait Doğrularının Eklenmesi

Üç boyutlu modellerimizin daha şık gözükebilmesi için köşe noktalarından devam eden çizgiler göstermek oldukça iyi olacaktır. Bu işlemleri gerçekleştirebilmemiz için ScreenSpaceLines3D sınıfını kullanacağımızdan bahsetmiştik. XAML ve C# kod tarafında bu işlemleri nasıl yapabileceğimize göz atalım.

<Separator/>
<CheckBox Name="normalCheckBox">Doğruları Göster</CheckBox>
<TextBlock Text="Doğru Boyutu:"/>
<TextBox Name="normalBoyutTextBox" Text="1"/>

XAML tarafından kontrolleri ekledikten sonra bu kontroller eşliğinden işlem yapacak olan C# kodlarını da uygulamamıza ekleyelim.

private Model3DGroup NormalYapilandirma(Point3D n0, Point3D n1, Point3D n2, Vector3D normal)
{
Model3DGroup DogruGrup = new Model3DGroup();
Point3D n;
ScreenSpaceLines3D normal0Tel = new ScreenSpaceLines3D();
ScreenSpaceLines3D normal1Tel = new ScreenSpaceLines3D();
ScreenSpaceLines3D normal2Tel = new ScreenSpaceLines3D();
Color c = Colors.Blue;
int genislik = 1;
normal0Tel.Thickness = genislik;
normal0Tel.Color = c;
normal1Tel.Thickness = genislik;
normal1Tel.Color = c;
normal2Tel.Thickness = genislik;
normal2Tel.Color = c;
double num = 1;
double mult = .01;
double denom = mult * Convert.ToDouble(normalBoyutTextBox.Text);
double faktor = num / denom;
n = Vector3D.Add(Vector3D.Divide(normal, faktor), n0);
normal0Tel.Points.Add(n0);
normal0Tel.Points.Add(n);
n = Vector3D.Add(Vector3D.Divide(normal, faktor), n1);
normal1Tel.Points.Add(n1);
normal1Tel.Points.Add(n);
n = Vector3D.Add(Vector3D.Divide(normal, faktor), n2);
normal2Tel.Points.Add(n2);
normal2Tel.Points.Add(n);
this.anaGoruntuleme.Children.Add(normal0Tel);
this.anaGoruntuleme.Children.Add(normal1Tel);
this.anaGoruntuleme.Children.Add(normal2Tel);
return DogruGrup;
}

Kod tarafına eklememiz gereken tek bir kod parçası daha kaldı. O da, CheckBox ‘ta seçilmiş ise doğruların gözükmesidir. Bu işlemi için basit bir if koşulu eklememiz yeterli olacaktır.

if (normalCheckBox.IsChecked == true)
grup.Children.Add(NormalYapilandirma(n0, n1, n2, normal));

UcgenModeliOlustur() metodunun en alt kısmına bu if koşulunu eklediğimiz takdirde aşağıdaki gibi bir uygulamaya sahip olmuş oluruz.



Yeryüzü Oluşturma

3D teorisini anlatırken oluşturulan modellerin maksimum ve minimum noktalarının olduğunu ve bu noktaların rastgele verilmesi durumunda tümsekli eğrilere sahip bir bölgesel yüzey elde edebileceğimizden bahsetmiştik. Şimdi bu yüzeyi nasıl oluşturabileceğimize değineceğiz.

Kullanıcı arayüzümüze yüzey yapısının oluşturmalarını oluşturabilmesi için bir buton ekliyoruz.

<Button Name="yuzeyYapisiButon" Click="yuzeyYapisiButonClick">Yüzey Yapısı</Button>

Modellemeyi yaparken X ve Z koordinatları ile yüzeyi Y koordinatları ile ise yükseklikleri belirtiyoruz. Bu yapacağımız 10x10 ‘luk bir yüzey olacaktır. Yüzeyimizin çok daha gerçekçi olabilmesi için koordinat noktalarına ait değerleri rastgele oluştururuz. Bu işlemi yapmak için aşağıdaki koddan yararlanabilmek mümkündür.

private Point3D[] RastgeleYuzeyNoktalari()
{
//10x10 'luk yüzey oluşturuluyor..
Point3D[] noktalar = new Point3D[100];
Random r = new Random();
double y;
double denom = 1000;
int sayac = 0;
for (int z = 0; z < 10; z++)
{
for (int x = 0; x < 10; x++)
{
System.Threading.Thread.Sleep(1);
y = Convert.ToDouble(r.Next(1, 999)) / denom;
noktalar[sayac] = new Point3D(x, y, z);
sayac += 1;
}
}
return noktalar;
}

Rastgele nokta ürettikten sonra yuzeyYapisi butonunun tıklanması olayına aşağıdaki kodları ekliyoruz.

private void yuzeyYapisiButtonClick(object sender, RoutedEventArgs e)
{
ViewportTemizle();
KameraAlinan();
Model3DGroup yuzeyYapisi = new Model3DGroup();
Point3D[] noktalar = RastgeleYuzeyNoktalari();
for (int z = 0; z <= 80; z = z + 10)
{
for (int x = 0; x < 9; x++)
{
yuzeyYapisi.Children.Add(
UcgenModeliOlustur(
noktalar[x + z],
noktalar[x + z + 10],
noktalar[x + z + 1])
);
yuzeyYapisi.Children.Add(
UcgenModeliOlustur(
noktalar[x + z + 1],
noktalar[x + z + 10],
noktalar[x + z + 11])
);
}
}
ModelVisual3D model = new ModelVisual3D();
model.Content = yuzeyYapisi;
this.anaGoruntuleme.Children.Add(model);
}

Basit olarak çizimlerimizi oluşturmuş oluyoruz. Uygulamamızda Yüzey oluştur butonuna tıkladığımız hazırladığımız kod parçaları bize zig-zag olarak çizilmiş doğrular verecektedir. Bu doğruları da 3D nokta olarak tuttuğumuzdan ötürü bizlere tümsekli bir yeryüzüne benzeyen görüntü sunacaktır.



Uzantı doğrularını göstererek;


Tel görüntü Ekleme

Yüzey ile ilgili uygulamaları incelediğimizde bizlere uzantı noktaları ile birlikte tümsekler üzerinde daha belirleyici olması için tellere benzer çizimlerin yapıldığını görürüz. Şimdi bizde yüzey uygulamamıza bu telleri ekleyeceğiz.

Bu işlemi daha önceden doğruları gösterme işinde yaptığımız gibi CheckBox ile yaparsak sanırım çok daha kullanışlı olacaktır. Kullanıcı arayüzümüze XAML tarafında bu kontrolü ekliyoruz.

<Separator/>
<CheckBox Name="telGoruntuCheckBox">Telleri Goster</CheckBox>

Kontrolleri ekledikten sonra seçildiği zaman işlem görebilmesi için UcgenModeliOlustur() metoduna aşağıdaki koşulu ekliyoruz.

if (telGoruntuCheckBox.IsChecked == true)
{
ScreenSpaceLines3D telGoruntu = new ScreenSpaceLines3D();
telGoruntu.Points.Add(n0);
telGoruntu.Points.Add(n1);
telGoruntu.Points.Add(n2);
telGoruntu.Points.Add(n0);
telGoruntu.Color = Colors.LightBlue;
telGoruntu.Thickness = 3;
this.anaGoruntuleme.Children.Add(telGoruntu);
}

Metot ile bağlantılar bütün butonlarda sağlanmış olduğu için CheckBox ‘ı seçili duruma getirdiğimizde teller gözükecektir. Fakat ekran görüntüsünü göstermeden önce sizlere son olarak UcgenModeliOlustur() metodunun en son halini verelim. Çünkü içerisinde çok fazla oynama yaptık. Karıştırmış olabilme ihtimalinde, nerede hata yaptığınızı da bulabilmemiz mümkün olacaktır.

private Model3DGroup UcgenModeliOlustur(Point3D n0, Point3D n1, Point3D n2)
{
MeshGeometry3D mesh = new MeshGeometry3D();
mesh.Positions.Add(n0);
mesh.Positions.Add(n1);
mesh.Positions.Add(n2);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
Vector3D normal = NormalHesapla(n0, n1, n2);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
Material madde = new DiffuseMaterial(
new SolidColorBrush(Colors.DarkGoldenrod));
GeometryModel3D model = new GeometryModel3D(
mesh, madde);
Model3DGroup grup = new Model3DGroup();
grup.Children.Add(model);

if (normalCheckBox.IsChecked == true)
grup.Children.Add(NormalYapilandirma(n0, n1, n2, normal));

if (telGoruntuCheckBox.IsChecked == true)
{
ScreenSpaceLines3D telGoruntu = new ScreenSpaceLines3D();
telGoruntu.Points.Add(n0);
telGoruntu.Points.Add(n1);
telGoruntu.Points.Add(n2);
telGoruntu.Points.Add(n0);
telGoruntu.Color = Colors.LightBlue;
telGoruntu.Thickness = 3;
this.anaGoruntuleme.Children.Add(telGoruntu);
}

return grup;
}

Son olarak telli olarak yüzeyimiz aşağıdaki gibidir.



Burada yazımızın sonuna gelmiş bulunuyoruz. 3D teorisinden başlayarak tümsekli yüzey(topoloji) oluşturarak sonlandırmış oluyoruz. 3D modellemeyi incelerken daha anlaşılır olması açısından en basit anlamda anlatmaya çalıştık. Bu anlattıklarımızı rahatlıkla anlayabildiyseniz artık çok daha karmaşık 3D modellemeleri rahatlıkla yapabilmeniz mümkün olacaktır.

Umarım yararlı olabilmiştir.

Uygulamanın kaynak dosyasına linkten ulaşabilirsiniz.

Kaynaklar
CodePlex 3DTools App.
Microsoft Developer Network
Turhal Temizer, Karadeniz Teknik Üniversitesi, Bilgisayar ve İstatistik Bilimleri Bölümü, Bitirme Çalışması

Turhal Temizer
turhal.temizer@csharpnedir.com
http://turhal.blogspot.com/

Yorum Gönder

2 Yorumlar

yakup dedi ki…
Merhaba Turhal Hocam,

Bu uygulamanin sample codunu bana gonderirseniz cok sevinirim. Cok tesekkurler.

Email: sanliyakup63@hotmail.com
Adsız dedi ki…
öncelilikli olarak emeğinize sağlık. güzel anlatım. fakat örnek projeyi indirilemiyor. ilgilenirsen sevinirim.

Ad Code

Responsive Advertisement