58 Three.js 通過THREE.Raycaster給模型繫結點選事件
簡介
由於瀏覽器是一個2d
視口,而在裡面顯示three.js
的內容是3d
場景,所以,現在有一個問題就是如何將2d
視口的x
和y
座標轉換成three.js
場景中的3d
座標。好在three.js
已經有了解決相關問題的方案,那就是THREE.Raycaster
射線,用於滑鼠拾取(計算出滑鼠移過的三維空間中的物件)等等。我們看一張圖片:
這張圖片偷自:https://segmentfault.com/a/1190000010490845
我們一般都會設定三維場景的顯示區域,如果,指明當前顯示的2d
座標給THREE.Raycaster
的話,它將生成一條從顯示的起點到終點的一條射線。也就是說,我們再螢幕上點選了一個點,在three.js
THREE.Raycaster建構函式和物件方法
例項化
new Raycaster( origin, direction, near, far );
origin
— 光線投射的起點向量。
direction
— 光線投射的方向向量,應該是被歸一化的。
near
— 投射近點,用來限定返回比near要遠的結果。near不能為負數。預設為0。
far
— 投射遠點,用來限定返回比far要近的結果。far不能比near要小。預設為無窮大。
方法
setFromCamera
用一個新的原點和方向向量來更新射線(ray)。
.setFromCamera ( coords, camera )
coords
— 滑鼠的二維座標,在歸一化的裝置座標(NDC)中,也就是X 和 Y 分量應該介於 -1 和 1 之間。
camera
— 射線起點處的相機,即把射線起點設定在該相機位置處。
intersectObject
檢查射線和物體之間的所有交叉點(包含或不包含後代)。交叉點返回按距離排序,最接近的為第一個。 返回一個交叉點物件陣列。
.intersectObject ( object, recursive )
object
— 用來檢測和射線相交的物體。
recursive
— 如果為true,它還檢查所有後代。否則只檢查該物件本身。預設值為false。
返回陣列每一個物件的內容
[ { distance, point, face, faceIndex, indices, object }, ... ]
distance
– 射線的起點到相交點的距離
point
– 在世界座標中的交叉點
face
– 相交的面
faceIndex
– 相交的面的索引
indices
– 組成相交面的頂點索引
object
– 相交的物件
當一個網孔(Mesh)物件和一個快取幾何模型(BufferGeometry)相交時,faceIndex 將是 undefined,並且 indices 將被設定; 而當一個網孔(Mesh)物件和一個幾何模型(Geometry)相交時,indices 將是 undefined。
當計算這個物件是否和射線相交時,Raycaster 把傳遞的物件委託給 raycast 方法。 這允許 meshes 對於光線投射的響應可以不同於 lines 和 pointclouds。
注意,對於網格,面(faces)必須朝向射線原點,這樣才能被檢測到;通過背面的射線的交叉點將不被檢測到。 為了光線投射一個物件的正反兩面,你得設定 material 的 side 屬性為 THREE.DoubleSide。
點選事件實現案例
//宣告raycaster和mouse變數
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick( event ) {
//通過滑鼠點選的位置計算出raycaster所需要的點的位置,以螢幕中心為原點,值的範圍為-1到1.
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// 通過滑鼠點的位置和當前相機的矩陣計算出raycaster
raycaster.setFromCamera( mouse, camera );
// 獲取raycaster直線和所有模型相交的陣列集合
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects);
//將所有的相交的模型的顏色設定為紅色,如果只需要將第一個觸發事件,那就陣列的第一個模型改變顏色即可
for ( var i = 0; i < intersects.length; i++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
}
window.addEventListener( 'click', onMouseClick, false );
思路:先通過當前點選位置的座標和相機計算出raycaster
線,然後計算出相交的所有模型,修改模型的紋理。
案例程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
html, body {
margin: 0;
height: 100%;
}
canvas {
display: block;
}
</style>
</head>
<body onload="draw();">
</body>
<script src="https://johnson2heng.github.io/three.js-demo/lib/three.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/controls/OrbitControls.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/libs/stats.min.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/libs/dat.gui.min.js"></script>
<script>
var renderer;
function initRender() {
renderer = new THREE.WebGLRenderer({antialias: true,alpha:true});
renderer.setSize(window.innerWidth, window.innerHeight);
//告訴渲染器需要陰影效果
//renderer.shadowMap.enabled = true;
//renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 預設的是,沒有設定的這個清晰 THREE.PCFShadowMap
renderer.setClearColor(0xffffff);
document.body.appendChild(renderer.domElement);
}
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 40, 100);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
var scene;
function initScene() {
scene = new THREE.Scene();
}
function initLight() {
}
function initModel() {
//輔助工具
var helper = new THREE.AxesHelper(10);
scene.add(helper);
var s = 25;
var cube = new THREE.CubeGeometry(s, s, s);
for (var i = 0; i < 3000; i++) {
var material = new THREE.MeshBasicMaterial({color:randomColor()});
var mesh = new THREE.Mesh(cube, material);
mesh.position.x = 800 * ( 2.0 * Math.random() - 1.0 );
mesh.position.y = 800 * ( 2.0 * Math.random() - 1.0 );
mesh.position.z = 800 * ( 2.0 * Math.random() - 1.0 );
mesh.rotation.x = Math.random() * Math.PI;
mesh.rotation.y = Math.random() * Math.PI;
mesh.rotation.z = Math.random() * Math.PI;
mesh.updateMatrix();
scene.add(mesh);
}
}
//隨機生成顏色
function randomColor() {
var arrHex = ["0","1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d","e","f"],
strHex = "#",
index;
for(var i = 0; i < 6; i++) {
index = Math.round(Math.random() * 15);
strHex += arrHex[index];
}
return strHex;
}
//宣告raycaster和mouse變數
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick( event ) {
//通過滑鼠點選的位置計算出raycaster所需要的點的位置,以螢幕中心為原點,值的範圍為-1到1.
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// 通過滑鼠點的位置和當前相機的矩陣計算出raycaster
raycaster.setFromCamera( mouse, camera );
// 獲取raycaster直線和所有模型相交的陣列集合
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects);
//將所有的相交的模型的顏色設定為紅色,如果只需要將第一個觸發事件,那就陣列的第一個模型改變顏色即可
for ( var i = 0; i < intersects.length; i++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
}
window.addEventListener( 'click', onMouseClick, false );
//初始化dat.GUI簡化試驗流程
var gui;
function initGui() {
//宣告一個儲存需求修改的相關資料的物件
controls = {
};
var gui = new dat.GUI();
}
//初始化效能外掛
var stats;
function initStats() {
stats = new Stats();
document.body.appendChild(stats.dom);
}
//使用者互動外掛 滑鼠左鍵按住旋轉,右鍵按住平移,滾輪縮放
var controls;
function initControls() {
controls = new THREE.OrbitControls(camera, renderer.domElement);
// 如果使用animate方法時,將此函式刪除
//controls.addEventListener( 'change', render );
// 使動畫迴圈使用時阻尼或自轉 意思是否有慣性
controls.enableDamping = true;
//動態阻尼係數 就是滑鼠拖拽旋轉靈敏度
//controls.dampingFactor = 0.25;
//是否可以縮放
controls.enableZoom = true;
//是否自動旋轉
controls.autoRotate = false;
//設定相機距離原點的最遠距離
controls.minDistance = 50;
//設定相機距離原點的最遠距離
controls.maxDistance = 200;
//是否開啟右鍵拖拽
controls.enablePan = true;
}
function render() {
renderer.render(scene, camera);
}
//視窗變動觸發的函式
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
render();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
//更新控制器
render();
//更新效能外掛
stats.update();
//controls.update();
requestAnimationFrame(animate);
}
function draw() {
initRender();
initScene();
initCamera();
initLight();
initModel();
initGui();
initControls();
initStats();
animate();
window.onresize = onWindowResize;
}
</script>
</html>