在three.js中,有一個Raycaster類可以做到上述需求,它的原裡是,想像有一條光束(ray)從Camera畫面中滑鼠指的位置,以垂直於Camera的X-Y平面的方式向Camera的負Z方向射出,並且計算在Scene的children中有哪些物件被這條線射中(ray可以穿過所有Object3D)。
在這裡,我要來示範一個使用three.js的Raycaster的範例,需求是這樣的:
需求:
- 畫面上有許多的Object3D物件,有各自的顏色。
- 當滑鼠指到(hover)某一個Object時,將指到的第一個Object變成紅色(即它後面的Object不會變色)。
- 滑鼠移開後,剛剛指到但現在沒指的Object變回原來的顏色。
接下來一步步的講解每一個步驟:
- 首先我們先設計主要的範例架構
- 設計html頁
<!DOCTYPE html> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script type="text/javascript" src="js/jquery-3.0.0.min.js"></script> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript" src="js/threeJsControls/OrbitControls.js"></script> <script type="text/javascript" src="js/main.js"></script> </head> <body> <div id="container" style='width:50%; height:500px; float: left;'></div> <div id="container2" style='width:50%; height:500px; float: left;'></div> </body> </html>
在這邊為了了解raycaster的運作方式,擺了兩個Camera,
其中<div id="container">是用來展示成果的地方。
而<div id="container2">是另一個Camera在另一個角度看到的樣子,我在滑鼠移動時不斷的在Scene中畫了一條由滑鼠射出去的ray的綠線,以便觀看raycaster的運作原理。 - 再來是Javascript的實作程式碼,相關的解釋都寫在註解中
$(document).ready(function () { var scene, camera, renderer, container, controls; var camera2, renderer2, container2, controls2; var onMouseMove = (function () { var selectedObjects = new Map(); //在object被變顏色之前,會先把object和原來的顏色存到這個Map, //Key是object、Value是原來的顏色 var rayLine; //用來顯示raycaster運作原裡的線,為THREE.Line return function (event) { //如果Scene上有rayLine,先把它除Scene中移除 if (rayLine) { scene.remove(rayLine); } var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); // 將滑鼠的x、y位置換算成-1~1的值,從renderer的畫面左下角(-1,-1),到右上解(1,1) mouse.x = ((event.clientX - container.offsetLeft + document.body.scrollLeft) / container.offsetWidth) * 2 - 1; mouse.y = -((event.clientY - container.offsetTop + document.body.scrollTop) / container.offsetHeight) * 2 + 1; //設置Raycaster,setFromCamera(對renderer來說的x,y位置:-1~1, -1~1,Camera物件) raycaster.setFromCamera(mouse.clone(), camera); //將Scene的children(即各物件)和raycaster一起計算,得到滑鼠選中的Object(ray會貫穿,不只一個) var intersects = raycaster.intersectObjects(scene.children); var isFoundInSelectedObjects = false; //選到的第一個Object有無存在selectedObjects中 //遍歷selectedObjects中的Object selectedObjects.forEach(function (value, key) { if (!!intersects[0] && key === intersects[0].object) { //如果selectedObjects中的Object是選到的第一個Object //直接改變Object的顏色 intersects[0].object.material.color.setHex(0xff0000); isFoundInSelectedObjects = true; } else { //如果selectedObjects中的Object不是選到的第一個Object //把Object的顏色(原來的顏色存在以Object為key的value中)改回來 //將Object的資料從selectedObjects中刪掉 key.material.color.setHex(value); selectedObjects.delete(key); } }); if (!!intersects[0] && !isFoundInSelectedObjects) { //如果選到的第一個Object沒有存紀錄在selectedObjects中 //將Object原來的顏色存進selectedObjects中,以Object為key selectedObjects.set(intersects[0].object, intersects[0].object.material.color.getHex()); //改變Object的顏色 intersects[0].object.material.color.setHex(0xff0000); } //////// //製造要標示raycaster運作原裡的線,THREE.Line var rayLineGeometry = new THREE.Geometry(); //設置兩點來畫線 rayLineGeometry.vertices.push(raycaster.ray.origin.clone()); rayLineGeometry.vertices.push(raycaster.ray.origin.clone().add(raycaster.ray.direction.clone().setLength(10000))); var rayLineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 }); var line = new THREE.Line(rayLineGeometry, rayLineMaterial); rayLine = line; scene.add(line); //加到Scene中 ////////// }; })(); //////////////////////////////////////////// function init() { //得到container的DOM container = document.getElementById("container"); container2 = document.getElementById("container2"); //設置camera和camera2 camera = new THREE.OrthographicCamera(container.offsetWidth / -1, container.offsetWidth / 1, container.offsetHeight / 1, container.offsetHeight / -1, 1, 10000); camera.position.set(2000, 2000, 2000); camera.zoom = 1; camera.updateProjectionMatrix(); camera2 = new THREE.OrthographicCamera(container.offsetWidth / -1, container.offsetWidth / 1, container.offsetHeight / 1, container.offsetHeight / -1, 1, 10000); camera2.position.set(3000, 2000, 1000); camera2.lookAt(new THREE.Vector3(0, 0, 0)); camera2.zoom = 1; camera2.updateProjectionMatrix(); // 設置scene scene = new THREE.Scene(); //在scene中產生各種Object generateObjects(scene); //設置renderer和renderer2 renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(container.offsetWidth, container.offsetHeight); renderer2 = new THREE.WebGLRenderer(); renderer2.setPixelRatio(window.devicePixelRatio); renderer2.setSize(container.offsetWidth, container.offsetHeight); //在container DOM下放置renderer的domElement以顯示畫面 container.appendChild(renderer.domElement); container2.appendChild(renderer2.domElement); //設置提供旋轉、平移、縮放Camera的controls controls = new THREE.OrbitControls(camera, container); controls2 = new THREE.OrbitControls(camera2, container2); //監聽滑鼠移動事件 window.addEventListener('mousemove', onMouseMove, false); } // Renders the scene and updates the render as needed. function animate() { requestAnimationFrame(render); } function render() { renderer.render(scene, camera); renderer2.render(scene, camera2); requestAnimationFrame(render); } function generateObjects(scene) { //產生27個不同顏色的方塊物件,並放到Scene中 for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { for (var k = 0; k < 3; k++) { var geometry = new THREE.BoxGeometry(100, 100, 100); var material = new THREE.MeshLambertMaterial({ color: 0x2194ce + (i + j + k) * 0xaaaaaa }); var mesh = new THREE.Mesh(geometry, material); mesh.position.set(-150 * 3 + 150 * i, -150 * 3 + 150 * j, -150 * 3 + 150 * k); scene.add(mesh); } } } //設置光線,light var spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(100, 1000, 100); spotLight.castShadow = true; spotLight.shadow.mapSize.width = 1024; spotLight.shadow.mapSize.height = 1024; spotLight.shadow.camera.near = 500; spotLight.shadow.camera.far = 4000; spotLight.shadow.camera.fov = 30; scene.add(spotLight); scene.add(new THREE.AmbientLight(0xdddddd)); } init(); animate(); });
原始碼下載:
threeJs_raycaster_test.7z
沒有留言 :
張貼留言