threejs点击事件
示例浏览地址:https://ithanmang.gitee.io/threejs/home/201807/20180703/02-raycasterDemo.html
双击鼠标左键选中模型并显示信息。 首先,解释一下三种坐标系的概念:场景坐标系(世界坐标系)、屏幕坐标系、视点坐标系。
场景坐标
通过three.js构建出来的场景,都具有一个固定不变的坐标系(无论相机的位置在哪),并且放置的任何物体都要以这个坐标系来确定自己的位置,也就是(0,0, 0)坐标。例如我们创建一个场景并添加箭头辅助。 屏幕坐标
在显示屏上的坐标就是屏幕坐标系。
如下图所示,其中的clientX和clientY的最值由,window.innerWidth,window.innerHeight决定。 视点坐标
视点坐标系就是以相机的中心点为原点,但是相机的位置,也是根据世界坐标系来偏移的,webGL会将世界坐标先变换到视点坐标,然后进行裁剪,只有在视线范围(视见体)之内的场景才会进入下一阶段的计算
如下图添加了相机辅助线. 射线检测
若想获取鼠标点击的物体,name就需要把屏幕坐标系转换为three.js中的三维坐标系。
three.js提供了一个类THREE.Raycaster可以用来解决这个问题。
看个示例图 THREE.Raycaster
THREE.Raycaster对象从屏幕上的点击位置向场景中发射一束光线。 // 计算出鼠标经过的3d空间中的对象
Raycaster( origin, direction, near, far ) { } 1 参数
origin — 射线的起点向量。
direction — 射线的方向向量,应该归一化。
near — 所有返回的结果应该比 near 远。Near不能为负,默认值为0。
far — 所有返回的结果应该比 far 近。Far 不能小于 near,默认值为无穷大。 2 方法
setFromCamera
用新的原点和方向来更新射线 方法名
.setFromCamera(coords : Vector2, camera : Camera ) : null
参数
coords - 鼠标的二维坐标,在归一化的设备坐标(NDC)中,也就是X 和 Y 分量应该介于 -1 和 1 之间。
camera - 射线起点处的相机,即把射线起点设置在该相机位置处。 **intersectObject **
来判断指定对象有没有被这束光线击中,返回被击中对象的信息,相交的结果会以一个数组的形式返回,其中的元素依照距离排序,越近的排在越前。 方法名
.intersectObject ( object, recursive : Boolean, optionalTarget : Array ) : Array
参数
object - 检测与射线相交的物体
recursive- 若为 true 则检查后代对象,默认值为false
optionalTarget - (可选参数)用来设置方法返回的设置结果。若不设置则返回一个实例化的数组。如果设置,必须在每次调用之前清除这个数组(例如,array.length= 0;) 返回值 Array [ { distance, point, face, faceIndex, object }, … ]
distance - 射线的起点到相交点的距离
point - 在世界坐标中的交叉点
face -相交的面
faceIndex - 相交的面的索引
object - 相交的对象
uv - 交点的二维坐标 当计算这个对象是否和射线相交时,Raycaster 把传递的对象委托给 raycast 方法。 这允许 meshes 对于光线投射的响应可以不同于 lines 和 pointclouds. 注意,对于网格,面(faces)必须朝向射线原点,这样才能被检测到;通过背面的射线的交叉点将不被检测到。 为了光线投射一个对象的正反两面,你得设置 material 的 side 属性为 THREE.DoubleSide **intersectObjects **
intersectObjects 与 intersectObject 类似,除了传入的参数是一个数组之外,并无大的差别。 方法名
.intersectObjects ( objects : Array, recursive : Boolean, optionalTarget : Array ) : Array
objects - 传入的参数。 3 主要代码
// 获取与射线相交的对象数组
function getIntersects(event) {
event.preventDefault();
console.log("event.clientX:"+event.clientX)
console.log("event.clientY:"+event.clientY) // 声明 raycaster 和 mouse 变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2(); // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
raycaster.setFromCamera(mouse, camera); // 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越近的越靠前
var intersects = raycaster.intersectObjects(scene.children); //返回选中的对象数组
return intersects;
}
导入外部模型注意事项
// 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越近的越靠前
var intersects = raycaster.intersectObjects(scene.children); 上面的raycaster.intersectObjects()的参数是scene.children,因为这里是测试的模型,没有涉及到外部模型的导入,但是再开发的时候我们一般都是对外部模型进行处理。
首先,你通过加载器把模型加载到场景中的时候需要在回调函数中打印一下加载进来的是一个什么对象,有可能是一个Mesh或者Group当然大部分模型资源基本上都是Group但是不排除还有别的类型例如Scene、Object…
此时,我们不能盲目的去直接把整个scene.children中的东西都放到raycaster.intersectObjects()来直接进行检测,因为整个scene.children中可能有一另一个scene或者是three.js不能识别的对象,所以我们需要先对加载进来的对象进行处理;
最好是先创建一个组对象new THREE.Group(),然后用这个组里面的对象来进行射线检测; 看下上面方法的第一个参数,是一个 Array,Group.children也是一个数组,所以我们可以把需要进行射线检测的物体放进一个组对象里面,便于处理; raycaster.intersectObjects(group.children); 元素按照距离排序,越近的越靠前
这句话的意思是,首先,点击或者触发方法创建THREE.Raycaster()对象,然后从点击位置,发出一条射线,先被射线穿过的对象,会在数组中排序靠前。
例如我们从y轴对着球点击,然后看一下返回的数组: 返回了三个Mesh对象,因为这三个物体同时被从鼠标点击处发出的射线给穿透,因此都被返回,而球几何体离点击的位置最近,所以第一个元素就是球体。 4 动态创建DIV
部分代码 // 更新div的位置
function renderDiv(object) {
// 获取窗口的一半高度和宽度
var halfWidth = window.innerWidth / 2;
var halfHeight = window.innerHeight / 2; // 逆转相机求出二维坐标
var vector = object.position.clone().project(camera); // 修改 div 的位置
$("#label").css({
left: vector.x * halfWidth + halfWidth,
top: -vector.y * halfHeight + halfHeight - object.position.y
});
// 显示模型信息
$("#label").text("name:" + object.name);
} 这需要将场景坐标,转换成二维屏幕坐标。
首先,我们需要得到当前点在世界中的坐标位置,如果是某个场景组Group里面的模型的位置坐标那种,我们可以通过模型的方法localToWorld方法获取到世界坐标。
localToWorld方法名 .localToWorld ( vector : Vector3 ) : Vector3 作用:将矢量从本地空间坐标转换为世界坐标。\ 求出二维坐标
// 逆转相机求出二维坐标
var vector = object.position.clone().project(camera); 修改DIV的位置
通过求出的二维坐标,来计算位置。 left: vector.x * halfWidth + halfWidth,
top: -vector.y * halfHeight + halfHeight - object.position.y
示例完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>点击事件</title>
<style>
body {
margin: ;
overflow: hidden;
} #label {
position: absolute;
padding: 10px;
background: rgba(, , , 0.6);
line-height: ;
border-radius: 5px;
}
</style>
<script src="../../libs/build/three.js"></script>
<script src="../../libs/jquery-1.9.1.js"></script>
<script src="../../libs/examples/js/Detector.js"></script>
<script src="../../libs/examples/js/controls/TrackballControls.js"></script>
<script src="../../libs/examples/js/libs/dat.gui.min.js"></script>
<script src="../../libs/examples/js/libs/stats.min.js"></script>
</head>
<body>
<div id="WebGL-output"></div>
<div id="Stats-output"></div> <div id="label"></div>
<script> var stats = initStats();
var scene, camera, renderer, controls, light, selectObject; // 场景
function initScene() {
scene = new THREE.Scene();
} // 相机
function initCamera() {
camera = new THREE.PerspectiveCamera(, window.innerWidth / window.innerHeight, 0.1, );
camera.position.set(, , );
camera.lookAt(new THREE.Vector3(, , ));
} // 渲染器
function initRenderer() {
if (Detector.webgl) {
renderer = new THREE.WebGLRenderer({antialias: true});
} else {
renderer = new THREE.CanvasRenderer();
}
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x050505);
document.body.appendChild(renderer.domElement);
} // 初始化模型
function initContent() {
var helper = new THREE.GridHelper(, , 0xCD3700, 0x4A4A4A);
scene.add(helper); var cubeGeometry = new THREE.BoxGeometry(, , );
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x9370DB});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.y = ;
cube.name = "cube";
scene.add(cube); var sphereGeometry = new THREE.SphereGeometry(, , , );
var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x3CB371});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = ;
sphere.position.y = ;
sphere.name = "sphere";
// sphere.position.z = 200;
scene.add(sphere); var cylinderGeometry = new THREE.CylinderGeometry(, , , );
var cylinderMaterial = new THREE.MeshLambertMaterial({color: 0xCD7054});
var cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
cylinder.position.x = -;
cylinder.position.y = ;
cylinder.name = "cylinder";
// cylinder.position.z = -200;
scene.add(cylinder);
} // 鼠标双击触发的方法
function onMouseDblclick(event) { // 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
var intersects = getIntersects(event); // 获取选中最近的 Mesh 对象
if (intersects.length != && intersects[].object instanceof THREE.Mesh) {
selectObject = intersects[].object;
changeMaterial(selectObject);
} else {
alert("未选中 Mesh!");
}
} // 获取与射线相交的对象数组
function getIntersects(event) {
event.preventDefault();
console.log("event.clientX:"+event.clientX)
console.log("event.clientY:"+event.clientY) // 声明 raycaster 和 mouse 变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2(); // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
mouse.x = (event.clientX / window.innerWidth) * - ;
mouse.y = -(event.clientY / window.innerHeight) * + ; //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
raycaster.setFromCamera(mouse, camera); // 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
var intersects = raycaster.intersectObjects(scene.children); //返回选中的对象
return intersects;
} // 窗口变动触发的方法
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
} // 键盘按下触发的方法
function onKeyDown(event) {
switch (event.keyCode) {
case :
initCamera();
initControls();
break;
}
} // 改变对象材质属性
function changeMaterial(object) { var material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
transparent: object.material.transparent ? false : true,
opacity: 0.8
});
object.material = material;
} // 初始化轨迹球控件
function initControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// controls.noRotate = true;
controls.noPan = true;
} // 初始化灯光
function initLight() {
light = new THREE.SpotLight(0xffffff);
light.position.set(-, , -);
light.castShadow = true; scene.add(light);
scene.add(new THREE.AmbientLight(0x5C5C5C));
} // 初始化 dat.GUI
function initGui() {
// 保存需要修改相关数据的对象
gui = new function () { }
// 属性添加到控件
var guiControls = new dat.GUI();
} // 初始化性能插件
function initStats() {
var stats = new Stats(); stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px'; document.body.appendChild(stats.domElement);
return stats;
} // 更新div的位置
function renderDiv(object) {
// 获取窗口的一半高度和宽度
var halfWidth = window.innerWidth / ;
var halfHeight = window.innerHeight / ; // 逆转相机求出二维坐标
var vector = object.position.clone().project(camera); // 修改 div 的位置
$("#label").css({
left: vector.x * halfWidth + halfWidth,
top: -vector.y * halfHeight + halfHeight - object.position.y
});
// 显示模型信息
$("#label").text("name:" + object.name);
} // 更新控件
function update() {
stats.update();
controls.update();
controls.handleResize();
} // 初始化
function init() {
initScene();
initCamera();
initRenderer();
initContent();
initLight();
initControls();
initGui();
addEventListener('dblclick', onMouseDblclick, false);
addEventListener('resize', onWindowResize, false);
addEventListener('keydown', onKeyDown, false);
} function animate() {
if (selectObject != undefined && selectObject != null) {
renderDiv(selectObject);
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
update();
} init();
animate(); </script>
</body>
</html>
threejs点击事件的更多相关文章
- threejs Object的点击(鼠标)事件(获取点击事件的object)
objects=[]; raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); //监听全局点击事件,通过ray检测选中哪一个o ...
- Jquery的点击事件,三句代码完成全选事件
先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...
- Android笔记——Button点击事件几种写法
Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...
- 关于Android避免按钮重复点击事件
最近测试人员测试我们的APP的时候,喜欢快速点击某个按钮,出现一个页面出现多次,测试人员能不能禁止这样.我自己点击了几下,确实存在这个问题,也感觉用户体验不太好.于是乎后来我搜了下加一个方法放在我们U ...
- js 基础篇(点击事件轮播图的实现)
轮播图在以后的应用中还是比较常见的,不需要多少行代码就能实现.但是在只掌握了js基础知识的情况下,怎么来用较少的而且逻辑又简单的方法来实现呢?下面来分析下几种不同的做法: 1.利用位移的方法来实现 首 ...
- Android中点击事件的实现方式
在之前博文中多次使用了点击事件的处理实现,有朋友就问了,发现了很多按钮的点击实现,但有很多博文中使用的实现方式有都不一样,到底是怎么回事.今天我们就汇总一下点击事件的实现方式. 点击事件的实现大致分为 ...
- click事件的累加绑定,绑定一次点击事件,执行多次
最近做项目为一个添加按钮绑定点击事件,很简单的一个事情,于是我按照通常做法找到元素,使用jquery的on()方法为元素绑定了点击事件,点击同时发送请求.完成后看效果,第一次点击没有问题.再一次点击后 ...
- Android开发-之监听button点击事件
一.实现button点击事件的方法 实现button点击事件的监听方法有很多种,这里总结了常用的四种方法: 1.匿名内部类 2.外部类(独立类) 3.实现OnClickListener接口 4.添加X ...
- Android 防止多次点击事件
恐怕大家都会遇到这样的问题,一个点击事件多次触发,导致,同样的内容提交了多次,或者说弹出多个页面... 下面是简单的方案,大家可以试一试 原理很简单,当我们第一次点击的时候,把按钮变成不可点击状态. ...
随机推荐
- thinkphp 前后端分离
thinkphp 前后端分离 简单记录一下之前学习tp的历程吧. 前端HTML页面渲染 <?php namespace app\index\controller; use think\Contr ...
- 单调栈-Maximum Width Ramp
2020-01-23 19:39:26 问题描述: 问题求解: public int maxWidthRamp(int[] A) { Stack<Integer> stack = new ...
- [ex-kmp] HDU 2019 Multi-University Training Contest 5-string matching
string matching Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others ...
- junit Mockito使用入门
junit Mockito使用入门 准备 在我们进一步讨论之前,让我们探索几种不同的方法来启用Mockito测试中注释的使用. 方式一 MockitoJUnitRunner 我们拥有的第一个选择是使用 ...
- CF 997A
You’ve got a string a1,a2,…,an, consisting of zeros and ones.Let’s call a sequence of consecutive el ...
- 用libevent写个简单的server/client
libevent是一个轻量级的事件触发库,可以很好地利用在网络通讯上面,用其进行大量的异步,延时,重发等场景. 下面是一个server的demo #include include void cb_fu ...
- 面试刷题30:SpringBean的生命周期?
spring是Java软件开发的事实标准. 我是李福春,我在准备面试,今天的问题是:springBean的生命周期是怎样的? 答:spring最基础的能力是IOC(依赖注入),AOP(面向切面编程), ...
- C#中的字符串处理
C#中的字符串处理 是由多个单个字符组成的.字符串的关键字是string,而我们单个字符char型.也就是一个字符串可以分为很多个char的字符.注意 同时,我们在开发项目或者学习时.更多的操作不是数 ...
- 1+X Web前端开发(中级)理论考试样题(附答案)
传送门 教育部:职业教育将启动"1+X"证书制度改革 职业教育改革1+X证书制度试点启动 1+X成绩/证书查询入口 一.单选题(每小题2分,共30小题,共 60 分) 1.在Boo ...
- 从汇编代码理解 Block 的内存结构
❓ 在断点调试 iOS 程序碰到 block 作为函数的形参时,如果想知道该 block 本身的函数签名信息和函数体地址时,有哪些办法?