1. 程式人生 > >如何實現對象交互

如何實現對象交互

真的 步驟 修改 res 屬性 金字塔 對象 box .get

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

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

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

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

畫布交互,你可以增加方格到畫布上,也可以移出它;

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

1.如何工作

在寫代碼之前,了解計算機3D圖形是如何工作是非常有幫助的,即使是非常粗糙的方式。我們如何從抽象的3D場景映射到我們屏幕中的2D圖像?

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

為什麽需要知道反向操作?你可能會提這樣的問題。那麽,如果你想知道你的鼠標指針下面是哪個對象,你需要把這些2D坐標重新轉換到3D坐標中,然後才能確定是選擇的那個3D對象。這叫做“反投影”。所有得到以下兩個定義:

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

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

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

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

技術分享

圖片中,左邊是一個抽象的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 );
}

所有的集合對象都使用同一個材質,它這些材質的顏色是不同的,每個立方體都設置了一種隨機的灰度顏色。接下來,我們準備兩個關鍵對象:投影對象、鼠標坐標。

projector = new THREE.Projector();
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坐標。但笛卡爾坐標的卻如下所示:

技術分享

理解了這兩個坐標系,上面的代碼你就知道為什麽會那樣寫了。

現在我們將使用mouseVector和camera生成一個射線對象:

var raycaster = projector.pickingRay( mouseVector.clone(), camera );

這裏我們克隆了mouseVector,而表示直接傳遞它。那是因為pickingRay函數內部會修改mouseVector的值,你可以查看Projector.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的功能。

如何實現對象交互