示例浏览地址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. Linux下反弹shell笔记

    0x00 NC命令详解 在介绍如何反弹shell之前,先了解相关知识要点. nc全称为netcat,所做的就是在两台电脑之间建立链接,并返回两个数据流 可运行在TCP或者UDP模式,添加参数 —u 则 ...

  2. 【Excel使用技巧】vlookup函数

    背景 前不久开发了一个运营小工具,运营人员上传一个id的列表,即可导出对应id的额外数据.需求本身不复杂,很快就开发完了,但上线后,运营反馈了一个问题,导出后的数据跟导出之前的数据顺序不一致. 经过沟 ...

  3. [Linux系统] CentOS7(RHEL7)重置root用户密码

    1.系统启动时,按"e"进入编辑界面 2.编辑内容 将rhgb quiet修改为 init=/bin/sh : 然后按 ctrl+x . 3.修改root密码 mount -o r ...

  4. 翻转-Flip Columns For Maximum Number of Equal Rows

    2020-02-20 11:00:06 问题描述: 问题求解: 翻转题一个常见的思路就是站在结束的状态来反推最初的状态,本题的解题思路就是站在结束的时候的状态来进行反推. 如果在最终的状态i-row是 ...

  5. 李宏毅老师机器学习课程笔记_ML Lecture 0-2: Why we need to learn machine learning?

    引言: 最近开始学习"机器学习",早就听说祖国宝岛的李宏毅老师的大名,一直没有时间看他的系列课程.今天听了一课,感觉非常棒,通俗易懂,而又能够抓住重点,中间还能加上一些很有趣的例子 ...

  6. 「面试指南」JS数组Array常用算法,Array算法的一般解答思路

    先看一道面试题 在 LeetCode 中有这么一道简单的数组算法题: // 给定一个整数数组 nums 和一个目标值 target, // 请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下 ...

  7. Oracle 11g Windows下安装出现INS-30131错误

    1.错误信息 2.解决方法 问题分析:访问Temp文件夹缺少权限 方案1: 使用net use查看C盘是否共享,否则进行设置,这种方法网上有很多帖子,不详坠 方案2:打开cmd,到相应的解压后的set ...

  8. JSP+Servlet+C3P0+Mysql实现的azhuo商城

    项目简介 项目来源于:https://gitee.com/xuyizhuo/shopping 原仓库中缺失jar包及sql文件异常,现将修改过的源码上传到百度网盘上. 链接:https://pan.b ...

  9. Ubuntu16.04下安装搜狗输入法及实现中英文转换问题

    1.问题描述 版本信息:Ubuntu16.04 解决问题:搜狗输入法的安装 2.解决办法 STEP1:搜索搜狗输入法for Linux --> 选择64bit --> 下载得到一个sogo ...

  10. Python如何用virtualenv搭建虚拟环境

    虚拟环境的搭建 优点 1.使不同应用开发环境相互独立 2.环境升级不影响其他应用,也不会影响全局的python环境 3.防止出现包管理混乱及包版本冲突 windows 安装 # 建议使用pip3安装到 ...