如何實現對象交互


    在本篇隨筆中,我們學習下什么是對象選擇,投影和反投影是如何工作的,怎樣使用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鼠標的位置投射一根光線,沿着相機的當前方向,看射線是否投射到任何對象上。如果是,那么我們就選中了某個對象。沒有,那么我們就沒選中如何對象。

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

    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的功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM