如何實現對象交互

    在本篇隨筆中,咱們學習下什麼是對象選擇,投影和反投影是如何工做的,怎樣使用Three.js構建可以使用鼠標和對象交互的應用。例如當鼠標移到對象,對象變成紅色,鼠標移走,對象又恢復原來的顏色。html

    本篇隨筆的源代碼來自於:https://github.com/sole/three.js-tutorials/tree/master/object_pickinggit

    這裏還有更多的例子可供參考:github

    和立方體交互,當你在立方體盒子上點擊,鼠標和立方體交互的點會出現一個黑點;web

    畫布交互,你能夠增長方格到畫布上,也能夠移出它;canvas

    當你在操做這些例子,會防線它們有一個共同特性。咱們使用的2D座標系(屏幕)檢測3D空間中的對象。這就是對象的選擇。app

1.如何工做

    在寫代碼以前,瞭解計算機3D圖形是如何工做是很是有幫助的,即便是很是粗糙的方式。咱們如何從抽象的3D場景映射到咱們屏幕中的2D圖像?dom

    當你使用相機渲染場景時,一大堆數據機制開始對3D場景進行運算和處理,以便生成可從相機看到的場景片斷的2D表示。有許多步驟涉及,但咱們這裏感興趣的是投影,就是它將3D的對象變成了屏幕中的2D實體。那若是反向操做又是怎樣的?函數

    爲何須要知道反向操做?你可能會提這樣的問題。那麼,若是你想知道你的鼠標指針下面是哪一個對象,你須要把這些2D座標從新轉換到3D座標中,而後才能肯定是選擇的那個3D對象。這叫作「反投影」。全部獲得如下兩個定義:學習

    Porjection:從3D到2D的投影。spa

    UnProjection:反投影,從2D反向投影到3D。

    這裏還許紹一個步驟:一旦咱們反向投影到3D座標中,咱們怎麼確認是否選中了某個對象?答案是:投射光線。咱們從3D鼠標的位置投射一根光線,沿着相機的當前方向,看射線是否投射到任何對象上。若是是,那麼咱們就選中了某個對象。沒有,那麼咱們就沒選中如何對象。

    聽起來有些疑惑。咱們看看下面的圖片:

    firing rays!

    圖片中,左邊是一個抽象的3D場景,包含兩個立方體和一個金字塔。中間表明了咱們的屏幕。在屏幕上可看到咱們的目標位置。右邊是咱們視線角度的攝像頭,另外還有一條藍色射線,使用它來選擇對象。

    以上的介紹,讓咱們簡單瞭解了選擇對象的理論原理,接下來咱們看看在three.js中是如何體現這樣的過程。

對象選擇代碼實現

    在thres.js中實現這種的功能是很是簡單的。咱們先建立場景、渲染器、攝像頭等:

var container = document.getElementById( 'container' ),
    containerWidth, containerHeight,
    renderer,
    scene,
    camera;

containerWidth = container.clientWidth;
containerHeight = container.clientHeight;

renderer = new THREE.CanvasRenderer();
renderer.setSize( containerWidth, containerHeight );
container.appendChild( renderer.domElement );

renderer.setClearColorHex( 0xeeeedd, 1.0 );

scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera( 45, containerWidth / containerHeight, 1, 10000 );
camera.position.set( 0, 0, range * 2 );
camera.lookAt( new THREE.Vector3( 0, 0, 0 ) );

     上面的代碼都很是簡單,沒什麼可介紹的。接着,咱們添加一些對象。咱們建立灰色立方體而且把他們隨機設置他們的3D座標。我把這些全部的對象都存放在一個類型爲Object3D對象中。

geom = new THREE.CubeGeometry( 5, 5, 5 );

cubes = new THREE.Object3D();
scene.add( cubes );

for(var i = 0; i < 100; i++ ) {
        var grayness = Math.random() * 0.5 + 0.25,
                mat = new THREE.MeshBasicMaterial(),
                cube = new THREE.Mesh( geom, mat );
        mat.color.setRGB( grayness, grayness, grayness );
        cube.position.set( range * (0.5 - Math.random()), range * (0.5 - Math.random()), range * (0.5 - Math.random()) );
        cube.rotation.set( Math.random(), Math.random(), Math.random() ).multiplyScalar( 2 * Math.PI );
        cube.grayness = grayness; // *** NOTE THIS
        cubes.add( cube );
}

    全部的集合對象都使用同一個材質,它這些材質的顏色是不一樣的,每一個立方體都設置了一種隨機的灰度顏色。接下來,咱們準備兩個關鍵對象:射線對象、鼠標座標。

var raycaster = new THREE.Raycaster();
var mouseVector = new THREE.Vector3();

    當鼠標移動時,咱們想選擇對象。因此須要監聽mousemove事件:

window.addEventListener( 'mousemove', onMouseMove, false );

    而後,全部感興趣的功能都會在這個事件裏邊實現。當查看源代碼使,你須要特別當心下邊兩行代碼,這兩天代碼稍有差錯,可能咱們後面的選擇功能將沒法實現:

mouseVector.x = 2 * (e.clientX / containerWidth) - 1;
mouseVector.y = 1 - 2 * ( e.clientY / containerHeight );

    這兩行代碼將鼠標座標轉換爲 x、y範圍在(-1, 1)的笛卡爾座標。你可能主要到計算的y座標爲何是負的?那是由於經典的DOM座標系原點(0,0)是從左上角開始。往右是x軸,往下是y座標。但笛卡爾座標的卻以下所示:

    image

    理解了這兩個座標系,上面的代碼你就知道爲何會那樣寫了。

    如今咱們將使用mouseVector和camera生成具體的攝像方向:

raycaster.setFromCamera(mouseVector.clone(), camera);

    這裏咱們克隆了mouseVector,而表示直接傳遞它。那是由於setFromCamera函數內部會修改mouseVector的值,你能夠查看three.js源代碼看看,是否真的有修改。建立raycaster對象以後,咱們調用它的intersectObjects函數:

var intersects = raycaster.intersectObjects( cubes.children );

    傳遞的參數爲cubes.chidren,也就是說咱們要選擇的對象來自於cubes的children中。intersects將返回查詢一個選中對象的集合。而且某個對象包含了如下屬性:

    distance:攝像頭和對象有距離。

    point:在對象上表面上和射線交互的點的位置。

    face:對象和射線交互的面。

    object:和射線交互的對象。

    既然已經獲取到這些對象了,那麼咱們也能夠操做這些對象。首選咱們把全部對象的顏色復原爲以前設置的灰色:

cubes.children.forEach(function( cube ) {
    cube.material.color.setRGB( cube.grayness, cube.grayness, cube.grayness );
});

    接着咱們再設置交互的對象。把這些對象的顏色設置成紅色。

for( var i = 0; i < intersects.length; i++ ) {
    var intersection = intersects[ i ],
        obj = intersection.object;

    obj.material.color.setRGB( 1.0 - i / intersects.length, 0, 0 );
}

    以上就是OnMouseMove函數的全部代碼了,經過這些代碼咱們初步瞭解了選擇對象操做,其實咱們要寫的代碼不多,three.js已經幫咱們實現了具體的步驟。

    涉及到的鼠標操做功能不少,選擇對象是最基礎的,萬變不離其宗。像對象的拖動功能,選擇也是基礎功能。接下來咱們就再看看three.js是如何實現拖拽功能的。

three.js實現拖拽功能

    實現拖拽功能,主要使用了three.js的兩個擴展控件:TrackballControls和DragControls。

    首先,咱們先建立隨機位置的200個立方體:

var objects = [];
            var geometry = new THREE.BoxGeometry(40, 40, 40);
            for(var i = 0; i < 200; i++){
                var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
                    color: Math.random() * 0xffffff
                }));
                object.position.set(Math.random() * 1000 - 500, Math.random() * 600 - 300, Math.random() * 800 - 400);
                object.rotation.set(Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI);
                object.scale.set(Math.random() * 2 + 1, Math.random() * 2 + 1, Math.random() * 2 + 1);

                object.castShadow = true;
                object.receiveShadow = true;
                scene.add(object);
                objects.push(object);
            }

    爲了然各個立方體顯示隨機,每一個object都使用Math.random()函數隨機設置了position、rotation、scale。而且對象可產生投影可接收投影。

    接下來咱們建立剛纔提到的兩個控件:

var controls = new THREE.TrackballControls(camera);
            controls.rotateSpeed = 1.0;
            controls.zoomSpeed = 1.2;
            controls.panSpeed = 0.8;
            controls.noZoom = false;
            controls.noPan = false;
            controls.staticMoving = true;
            controls.dynamicDampingFactor = 0.3;
var dragControls = new THREE.DragControls(objects, camera, webGLRenderer.domElement);

    TrackballControls可用來經過旋轉移動攝像頭位置,實習整個場景的旋轉和移動。DragControls包含兩個事件:dragstart、dragend。

dragControls.addEventListener("dragstart", function(event){
                currentColor = event.object.material.color;
                event.object.material.color = new THREE.Color(0xffff00);
                event.object.material.transparent = true;
                event.object.material.opacity = 0.6;
                controls.enabled = false;
            });
            dragControls.addEventListener("dragend", function(event){
                event.object.material.opacity = 1.0;
                event.object.material.color = currentColor;
                controls.enabled = true;
            });

    dragStart事件表示開始執行拖拽了,而dragend表示拖拽結束。可經過event.object獲取當前拖拽的對象,而後就能夠設置對象的屬性了。這裏須要特別注意的是,在拖拽開始時,咱們須要禁止TrackballControls功能,纔可以拖動物體。因此須要設置controls.enabled = false。當拖動結束,設置controls.enabled = true恢復TrackballControls的功能。

相關文章
相關標籤/搜索