示例浏览地址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点击事件的更多相关文章

  1. threejs Object的点击(鼠标)事件(获取点击事件的object)

    objects=[]; raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); //监听全局点击事件,通过ray检测选中哪一个o ...

  2. Jquery的点击事件,三句代码完成全选事件

    先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...

  3. Android笔记——Button点击事件几种写法

    Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...

  4. 关于Android避免按钮重复点击事件

    最近测试人员测试我们的APP的时候,喜欢快速点击某个按钮,出现一个页面出现多次,测试人员能不能禁止这样.我自己点击了几下,确实存在这个问题,也感觉用户体验不太好.于是乎后来我搜了下加一个方法放在我们U ...

  5. js 基础篇(点击事件轮播图的实现)

    轮播图在以后的应用中还是比较常见的,不需要多少行代码就能实现.但是在只掌握了js基础知识的情况下,怎么来用较少的而且逻辑又简单的方法来实现呢?下面来分析下几种不同的做法: 1.利用位移的方法来实现 首 ...

  6. Android中点击事件的实现方式

    在之前博文中多次使用了点击事件的处理实现,有朋友就问了,发现了很多按钮的点击实现,但有很多博文中使用的实现方式有都不一样,到底是怎么回事.今天我们就汇总一下点击事件的实现方式. 点击事件的实现大致分为 ...

  7. click事件的累加绑定,绑定一次点击事件,执行多次

    最近做项目为一个添加按钮绑定点击事件,很简单的一个事情,于是我按照通常做法找到元素,使用jquery的on()方法为元素绑定了点击事件,点击同时发送请求.完成后看效果,第一次点击没有问题.再一次点击后 ...

  8. Android开发-之监听button点击事件

    一.实现button点击事件的方法 实现button点击事件的监听方法有很多种,这里总结了常用的四种方法: 1.匿名内部类 2.外部类(独立类) 3.实现OnClickListener接口 4.添加X ...

  9. Android 防止多次点击事件

    恐怕大家都会遇到这样的问题,一个点击事件多次触发,导致,同样的内容提交了多次,或者说弹出多个页面... 下面是简单的方案,大家可以试一试 原理很简单,当我们第一次点击的时候,把按钮变成不可点击状态. ...

随机推荐

  1. django中基于python3.6使用容联发送短信

    一. Django基于python3.6使用容联发送短信流程 容联官方的python支持2.7版本,当我们python解释器采用3版本时,需要修改容联接口中的一些参数及方法. 首先去容联官网注册账号, ...

  2. ImportError: libcusolver.so.8.0: cannot open shared object file: No such file or directory

    问题描述: ImportError: libcusolver.so.8.0: cannot open shared object file: No such file or directory 首先检 ...

  3. 洛谷P1957口算练习题题解

    前言: 题目传送门:https://www.luogu.com.cn/problem/P1957 其实这很简单 纯模拟撒~~~~ 正文开始: _话说 ,就当本蒟蒻正高高兴兴的刷水题时,居然 碰到了这个 ...

  4. Netty中ChannelHandler的生命周期

    在使用Netty进行网络编程的时候,通常需要在网络连接的不同阶段进行相应的操作,比如在连接建立时,客户端向服务端发起认证,在接收到数据时对数据内容进行解析等等.那么,连接的不同阶段在netty中如何表 ...

  5. Javascript之盒子拖拽(跟随鼠标、边界限定、轨迹回放)

    本文通过拖拽案例,实现"跟随鼠标.边界限定.轨迹回放"三大效果: 完整代码中有详尽注释,故不再进行细致讲解: 对于案例中需要注意的重点或易错点问题,会总结在最后. 效果图(仅演示左 ...

  6. Linux Ubuntu 开发环境配置 ——最具生产力工具一览

    Why Linux and Why exactly Ubuntu 首先这里就不做Mac,Linux,Windows三者之争了.只从个人角度分析下: Mac 不差钱(其实Mac作为超级本性价还行),不喜 ...

  7. 深入浅出C#结构体

    目录 1.应用背景 2.结构体解析 2.1.结构体存在栈中 2.2.结构体不需要手动释放 3.封装心跳包结构体 4.结构体静态帮助类 5.New出来的结构体是存在堆中还是栈中? 5.1.不带形参的结构 ...

  8. vue基础指令学习

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 微信APP支付-java后台实现

    不说废话,直接上代码 先是工具类(注意签名时要排序): import java.io.BufferedReader; import java.io.ByteArrayInputStream; impo ...

  10. CISP-PTE学习记录-大纲(1)

    大纲内容记录 Linux操作系统安全 Windows操作系统安全 数据库安全 Web安全基础 HHTP协议 注入漏洞 XSS漏洞 请求伪造 文件处理漏洞 访问控制漏洞 会话管理漏洞 实战练习 中间件 ...