Bu makalemizde, sahneye koyduğumuz şekilleri istediğimiz (x,y,z) koordinatlarına taşımayı, (x,y,z) eksenlerinde döndürmeyi ve istediğimiz eksenlerde boyutlandırmayı göreceğiz. Ayrıca makalenin sonuna doğru, 3D uygulama geliştirmede çok önemli olan gruplama özelliğini öğreneceğiz.

Örneklere başlamadan önce, 3D dünyasında yeni olanlar için ufak bir ön bilgilendirme gerekebilir.















Dünyanın merkezi nerede? Neresi Sağ, Neresi Sol?

2D dünyadan 3D dünyaya yeni geçiş yapanlar genellikle aşağıdaki sorularla karşılaşırlar.
  • (0,0,0) koordinatı ekranın neresinde duruyor?
  • Bir şeklin x koordinatını arttırınca, şekil sağa mı yoksa sola mı gider?
  • z koordinatı ekranın içine doğru gittikçe mi artar, dışarı çıktıkça mı?

Tüm bunlar, gerekli olan bir düşünce yapısı değişikliğinden (mind-shift) kaynaklanıyor. Three.JS normalde sağ-el koordinat sistemini kullanır. Bunun teorik anlamı, sağa doğru gittikçe x, yukarıya doğru gittikçe y ve ekranın dışına doğru gittikçe z değerinin artmasıdır. Ama nereye göre, kime göre?

Hatırlarsan "Emeklemeye Başlıyoruz" makalesinde Render işlemi için ne demiştik? "Kameranın bakış açısından, kameraya uygun perspektifte bir fotoğraf çekeriz ve belirlenen 2D yüzeye fotoğrafımızı yerleştiririz". Eğer kamera (0,0,10) koordinatlarında duruyorsa ve (0,0,0) koordinatına doğru bakıyorsa, yukarıda geçen teori doğrudur ve bu durumda (0,0,0) koordinatı ekranın tam ortasında durur.

Peki ya kamera (0,0,-10) koordinatında duruyor ve yine (0,0,0) koordinatına doğru bakıyorsa? Bu durumda (0,0,0) koordinatı ekranın yine tam ortasında durur ama bu sefer sola doğru gittikçe x değeri artar.

Peki bir de kameranın (0,10,10) koordinatında durduğunu ve (0,10,0) koordinatına baktığını farz edelim. Bu durumda (0,0,0) koordinatı artık ekranın ortasında değil, altında duruyor olacaktır. Çünkü kamera nereye bakıyorsa, görüntünün ortasında orası yer alır.

Çorba oldu değil mi? Bunu gerçek hayat gibi düşün. Bir taraftan bakınca sağ olan, karşı taraftan bakınca soldur. Bana göre karşımdaki nesne görüntü alanının ortasındadır. Bir başkasına göre kenarda duruyordur. Başka bir makalede anlatacağım lokal koordinat sisteminde her şey çok daha net olacak, şimdilik bunlara fazla takılma.


Bu makalemizdeki tüm örneklerimizde gariban bir kutuyu oradan oraya taşıyacak ve eğip bükeceğiz. İlk olarak aşağıdaki kodlarla gerekli olan HTML sayfamızı hazırlayalım.

<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1254" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="three-r61.min.js"></script>
<script src="program.js"></script>
</head>
<body>
</body>
</html>

Ardından aşağıdaki kodlarla "program.js" dosyamızı hazırlayalım.

"use strict";

var renderer, scene, camera, box;

$(document).ready(function(){

    addContainer();

    setStyle();

    prepareScene();

    addSphere();

    addBox();

    moveBox();

    animation();

});

function addContainer() {

    $("body").append("
<br>"); } function setStyle() { $("html, body, #Container").css("width", "100%"); $("html, body, #Container").css("height", "100%"); $("html, body, #Container").css("padding", "0"); $("html, body, #Container").css("margin", "0"); $("html, body, #Container").css("overflow", "hidden"); $("html, body, #Container").css("backgroundColor", "#000000"); } function prepareScene() { var container = $("#Container"); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.width(), container.height()); container.append(renderer.domElement); scene = new THREE.Scene(); var aspectRatio = container.width() / container.height(); camera = new THREE.PerspectiveCamera(45, aspectRatio, 0.1, 20000); camera.position.set(50, 70, 300); var ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 0.9); scene.add(ambientLight); } function addSphere() { var sphere = new THREE.Mesh(new THREE.SphereGeometry(5, 16, 16), new THREE.MeshLambertMaterial({ color: 0x00ff00 })); scene.add(sphere); } function addBox() { box = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 })); scene.add(box); } function moveBox() { box.position = new THREE.Vector3(-30, 0, 30); box.position.y = 20; } function animation() { renderer.render(scene, camera); requestAnimationFrame(animation); }

Bu örneğin hazır yazılmışını çalıştırmak için buraya tıklayabilirsin. Karşına aşağıdaki gibi bir sayfanın gelmesi gerekiyor.


Kod biraz uzun gibi gözüküyor ama gözünü korkutmasın. Bir önceki makalede kullandığımız örnekten pek bir farkı yok. Ayrıca bütün makale boyunca birkaç satırı değiştirip kullanacağız.

Kodu incelersen, bir önceki makalede sahneye eklediğimiz küp ve kürenin aynı şekilde eklendiğini göreceksin. Buraya kadar bilmediğin bir şey yok. Yalnız bu sefer kürenin yarıçapını 5 birim verdim. Çünkü burada küreyi, (0,0,0) koordinatının nerede olduğunu bize göstermesi için küçük bir şekilde ekledim. Böylece kutunun ilk pozisyonundan başka bir koordinata taşındığını fark edebileceğiz.

Not : Sahneye eklenen her şekil, müdahale edilmezse ilk başta (0,0,0) koordinatına yerleşir.

Kodu incelemeye devam edersen, bir önceki makaleden farklı olarak "moveBox" fonksiyonunu göreceksin. Bu fonksiyon içerisinde, şeklimizi taşımanın iki farklı yolunu görüyoruz. Aslında bunlardan sadece bir tanesi de şekli taşımak için yeterlidir ancak iki yöntemi de göstermek için özellikle ikisini de ekledim. Hadi bunları inceleyelim.

    box.position = new THREE.Vector3(-30, 0, 30);

"addBox" fonksiyonu içerisinde kutumuzu sahneye eklerken nesne adı olarak "box" vermiştik. Burada "box" nesnemizin "position" özelliğini değiştirerek, şeklin sahnede belirttiğimiz koordinata gitmesini sağlıyoruz. "position" özelliğine değer atamak için 3D koordinatı belirten bir "Vector3" sınıfından faydalanıyoruz. Sınıfa geçilen parametreler sırasıyla (x,y,z) koordinatlarıdır. Bu satırdan sonra şeklimiz (-30,0,30) koordinatlarına gitmiş oluyor. Bu yöntem, bir seferde üç eksende de koordinat bildirmek için kullanılır.

    box.position.y = 20;

Burada ise şekli sadece y ekseninde taşıyoruz ve y = 20 koordinatına gitmesini, geri kalan eksen koordinatlarının değişmemesini söylüyoruz. Bu satırdan sonra şeklimiz (-30,20,30) koordinatlarına gitmiş oluyor. Bu yöntem, bir seferde tek bir eksende koordinat bildirmek için kullanılır.

Hepsi bu kadar. Bu iki yöntemle sahnedeki tüm şekilleri istediğimiz yere taşıyabiliriz. Sıra geldi döndürmeye. "program.js" dosyamızın içeriğini aşağıdaki kod ile değiştirelim.

"use strict";

var renderer, scene, camera, box;

$(document).ready(function () {

    addContainer();

    setStyle();

    prepareScene();

    addBox();

    rotateBox();

    animation();

});

function addContainer() {

    $("body").append("
<br>"); } function setStyle() { $("html, body, #Container").css("width", "100%"); $("html, body, #Container").css("height", "100%"); $("html, body, #Container").css("padding", "0"); $("html, body, #Container").css("margin", "0"); $("html, body, #Container").css("overflow", "hidden"); $("html, body, #Container").css("backgroundColor", "#000000"); } function prepareScene() { var container = $("#Container"); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.width(), container.height()); container.append(renderer.domElement); scene = new THREE.Scene(); var aspectRatio = container.width() / container.height(); camera = new THREE.PerspectiveCamera(45, aspectRatio, 0.1, 20000); camera.position.set(50, 70, 300); var ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 0.9); scene.add(ambientLight); } function addBox() { box = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 })); scene.add(box); } function rotateBox() { box.rotation.set(Math.PI / 4, 0, Math.PI / 4); box.rotation.y = Math.PI / 4; } function animation() { renderer.render(scene, camera); requestAnimationFrame(animation); }

Bu örneğin hazır yazılmışını çalıştırmak için buraya tıklayabilirsin. Karşına aşağıdaki gibi bir sayfanın gelmesi gerekiyor.



Bu örneğin bir önceki örnekten tek farkı, sahneden kürenin çıkmış olması (artık ihtiyacımız yok) ve "moveBox" fonksiyonunun kaldırılarak yerine "rotateBox"  fonksiyonunun gelmiş olması. Aynı şekilde bu fonksiyon içerisinde de şekli döndürmenin iki farklı yolunu göreceğiz. Tek tek gidelim.

    box.rotation.set(Math.PI / 4, 0, Math.PI / 4);

Burada "box" nesnemizin "rotation" özelliğini değiştirerek, şeklin sahnede belirttiğimiz radyan cinsinden açı miktarınca dönmesini sağlıyoruz. Sınıfa geçilen parametreler sırasıyla (x,y,z) eksenlerindeki dönüş için açı miktarıdır. Bu satırdan sonra şeklimiz x ve z eksenleri etrafında 45'er derece dönmüş oluyor. Bu yöntem, bir seferde üç eksende de dönüş açısı bildirmek için kullanılır.

    box.rotation.y = Math.PI / 4;

Burada ise şekli sadece y ekseni etrafında 45 derece döndürüyoruz. Bu satırdan sonra şeklimiz toplamda tüm eksenlerde 45'er derece dönmüş oluyor. Bu yöntem, bir seferde tek bir eksende dönüş açısı bildirmek için kullanılır.

Not : Math.PI = 180 derecedir.

Son olarak boyutlandırma işlemine bakacağız. "program.js" dosyamızın içeriğini aşağıdaki kod ile değiştirelim.

"use strict";

var renderer, scene, camera, box;

$(document).ready(function () {

    addContainer();

    setStyle();

    prepareScene();

    addBox();

    scaleBox();

    animation();

});

function addContainer() {

    $("body").append("
<br>"); } function setStyle() { $("html, body, #Container").css("width", "100%"); $("html, body, #Container").css("height", "100%"); $("html, body, #Container").css("padding", "0"); $("html, body, #Container").css("margin", "0"); $("html, body, #Container").css("overflow", "hidden"); $("html, body, #Container").css("backgroundColor", "#000000"); } function prepareScene() { var container = $("#Container"); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.width(), container.height()); container.append(renderer.domElement); scene = new THREE.Scene(); var aspectRatio = container.width() / container.height(); camera = new THREE.PerspectiveCamera(45, aspectRatio, 0.1, 20000); camera.position.set(50, 70, 300); var ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 0.9); scene.add(ambientLight); } function addBox() { box = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 })); scene.add(box); } function scaleBox() { box.scale.x = 4; box.scale.y = 0.5; } function animation() { renderer.render(scene, camera); requestAnimationFrame(animation); }

Bu örneğin hazır yazılmışını çalıştırmak için buraya tıklayabilirsin. Karşına aşağıdaki gibi bir sayfanın gelmesi gerekiyor.


Bu örneğin bir önceki örnekten tek farkı "rotateBox" fonksiyonunun kaldırılarak yerine "scaleBox"  fonksiyonunun gelmiş olması. Fonksiyonun içerisine bakalım.

    box.scale.x = 4;

    box.scale.y = 0.5;

Burada "box" nesnemizin "scale" özelliğini değiştirerek, şeklin belirttiğimiz eksenlerde boyutlandırılmasını sağlıyoruz. Burada 1 değeri (x1 olarak) boyutta herhangi bir değişikliğe gitmezken, 2 değeri (x2 olarak) şeklin belirtilen eksendeki boyutunun iki katına çıkmasını sağlar. Bu yöntemle şeklin x eksenindeki boyutunun 4 katına çıkmasını, y eksenindeki boyutunun ise yarıya düşmesini sağlamış olduk.

Sıra geldi son örneğimize. Şimdi anlatacaklarım çok önemli. Mümkün mertebe dikkatini vermeye çalış. Konumuz gruplama ve parent-child ilişkisi.

3D bir sahne tasarlarken, tüm şekiller birbirinden bağımsız şekilde hareket etmezler. Örneğin bir araba onlarca (belki de yüzlerce) alt parçadan oluşur. Gövde, tekerlekler, camlar, tamponlar vs. Eğer biz bunları sahneye tek tek ve birbirinden bağımsız şekilde eklersek, araba ileriye giderken tüm şekilleri ileri taşımamız, araba bir tarafa dönerken tüm şekilleri düzgün bir açıda döndürmemiz gerekir ki bu pratikte pek de altından kalkılabilecek bir iş değildir. Böyle durumlarda birbirleri ile ilişkili şekilleri kendi aralarında gruplarız ve bunlarla ilişkili alt şekilleri de kendi aralarında gruplayarak hiyerarşik bir yapı kurarız. Böylece asıl şekil hareket ederken, diğer şekiller de ona olan göreceli konumlarını korurlar. Şimdi bunu nasıl yaptığımızı görelim. "program.js" dosyamızın içeriğini aşağıdaki kod ile değiştirelim.

"use strict";

var renderer, scene, camera, objectGroup, box1, box2;

$(document).ready(function () {

    addContainer();

    setStyle();

    prepareScene();

    addObjectGroup();

    addBox1();

    addBox2();

    moveObjectGroup();

    animation();

});

function addContainer() {

    $("body").append("
<br>"); } function setStyle() { $("html, body, #Container").css("width", "100%"); $("html, body, #Container").css("height", "100%"); $("html, body, #Container").css("padding", "0"); $("html, body, #Container").css("margin", "0"); $("html, body, #Container").css("overflow", "hidden"); $("html, body, #Container").css("backgroundColor", "#000000"); } function prepareScene() { var container = $("#Container"); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.width(), container.height()); container.append(renderer.domElement); scene = new THREE.Scene(); var aspectRatio = container.width() / container.height(); camera = new THREE.PerspectiveCamera(45, aspectRatio, 0.1, 20000); camera.position.set(50, 70, 300); var ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 0.9); scene.add(ambientLight); } function addObjectGroup() { objectGroup = new THREE.Object3D(); scene.add(objectGroup); } function addBox1() { box1 = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 })); box1.position.x += 20; objectGroup.add(box1); } function addBox2() { box2 = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0x00ff00 })); box2.position.x -= 20; objectGroup.add(box2); } function moveObjectGroup() { objectGroup.position.y += 70; objectGroup.rotation.x = Math.PI / 4; } function animation() { renderer.render(scene, camera); requestAnimationFrame(animation); }

Bu örneğin hazır yazılmışını çalıştırmak için buraya tıklayabilirsin. Karşına aşağıdaki gibi bir sayfanın gelmesi gerekiyor.


Örneğimizde farklı olarak "addObjectGroup" adında bir fonksiyonumuz var. İçeriğine bakalım.

    objectGroup = new THREE.Object3D();

    scene.add(objectGroup);

Burada sanki 3D bir şekil gibi "Object3D" tipindeki sanal bir nesnemizi sahnemize ekliyoruz. Bu sanal nesne, 3D şekilleri gruplamak için kullanılır. "addBox1" ve "addBox2" isimli fonksiyonlarımızda da, nesnelerimizi artık "scene" nesnesine değil, buradaki nesneye ekliyoruz.

    objectGroup.add(box1);

    objectGroup.add(box2);

Son olarak "moveObjectGroup" fonksiyonu içerisinde, grubu Y ekseninde 70 birim yukarı taşıyor ve X ekseni etrafında 45 derece döndürüyoruz. Görüyoruz ki, gruba dahil olan iki şeklimiz de bu hareketlere bire bir uyuyor ancak aralarındaki mesafeyi de koruyorlar.

İhtiyacımıza göre bu gruba bağlı başka alt gruplar da tanımlayabilirdik. Burada önemli nokta şudur. Herhangi bir 3D şekil hareket ettiğinde, diğer şekilleri hareket ettirmez. Aynı şekilde herhangi bir grup hareket ettiğinde, bağlı olduğu üst grubu hareket ettirmez. Hareketler sadece hiyerarşik olarak yukarıdan aşağı doğru (Üst gruptan alt gruba ve alt nesnelere doğru) olur. Bu yüzden, arabanın alt objesi olan tekerlekler sağa-sola dönerken, bir yandan da araba ile birlikte ileriye doğru gidebilir.

Gruplama ve Parent-Child yöntemi 3D uygulama geliştirmede çok sık kullanılır. Örneğin bir insan kolu çizdiğimizde, parmak kemikleri birbirine bağlı, onlar da ele bağlı, el bileğe, bilek kola, kol dirseğe, dirsek üst kola, üst kol da omuza bağlı olur. Bu şekilde Parent-Child ilişkisini düzgün bir şekilde kurduktan sonra omuzun hafif kalkmasını söyleriz ve ona bağlı tüm alt grupların gerçekleştireceği aşırı kompleks hareketler otomatik olarak yerine getirilir.

Makalemizi tamamlarken öğrendiklerimizi özetleyelim.
  • Dünyanın merkezi ve sağ-sol meselelerine takılmıyoruz, okulumuzu bitiriyoruz.
  • Şekilleri taşımak için "position" özelliğini kullanırız.
  • Şekilleri döndürmek için "rotation" özelliğini kullanırız.
  • "rotation" özelliğine geçilen değerler Radyan cinsinden açıdır ve Math.PI = 180 derece anlamına gelir.
  • Şekilleri boyutlandırmak için "scale" özelliğini kullanırız.
  • "scale" özelliğine geçilen değerlerler, her bir eksende şeklin orijinal boyutları ile çarpılır. Bu sebeple 1 değeri o eksende herhangi bir değişikliğe sebep olmaz.
  • Birlikte hareket eden şekilleri Object3D nesnesi ile birbirine bağlarız ve birlikte hareketliliğin gerektirdiği kompleks hesaplamaların bizim adımıza otomatik yapılmasını sağlarız.


Yorum Gönder

 
Top