赞
踩
读了本文后的记录:https://blog.csdn.net/ithanmang/article/details/80897888
关于点击事件的练习和书写。
一、
three.js运行最好搭一个服务器,不然很容易有跨域等问题。下面代码也是,如果没有服务器,你直接跑html文件,会发现很模糊。
这里介绍npm搭建服务器的流程。
参考:https://www.jianshu.com/p/a162131e22d0
NPM全局安装
npm install -g live-server
使用方法:
在项目根目录使用命令
npm init -y 初始化一个npm项目
之后项目中找到package.json,修改scripts部分
"scripts": {
"server": "live-server ./ --port=8081"
}
或者
"scripts": {
"server": "live-server 某个目录"
}
二、
关于在script标签中使用import:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import
但是还是不懂得如何import,先放着,后面再说,问题不大,先学习。
ok现在开始学习。
步骤:
没得说,直接敲一遍代码,应该会运行不出来,然后改错。
错误:
没有把模型什么的加载进来。没有写initCont()方法。
少写了一个s,Obejects。
单击无法选中。mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;少写了负号。
通读一遍:
https://blog.csdn.net/ithanmang/article/details/80897888
整理知识点:
首先是程序大体的思路:
几位“大哥”是一定要有的,场景,相机,渲染器。物体,灯光,轨道控制器。动画函数也是必须,但是动画函数有点不同,这个后面说。
这里说了三种,一种是场景坐标系(世界坐标系),屏幕坐标系、视点坐标系。
场景坐标系:
就是通过右手定则出来的坐标系,它是通过three.js构建出来的场景,**是一个固定不变的坐标系(不管相机的位置在哪), 并且放置的任何物体都要以这个坐标系来确定自己的位置,也就是(0, 0, 0)坐标。**这个概念很重要,可以让你随心所欲地将任何物体放置在任何位置。
例如我们创建一个场景并添加箭头辅助。
屏幕坐标系:
在显示屏上的坐标就是屏幕坐标系。(这个好理解,就是你面前的,以左上角为(0, 0)原点,想右下方向展开的坐标系)
如下图所示,其中的clientX和clientY的最值由,window.innerWidth,window.innerHeight决定。
视点坐标系
视点坐标系就是以相机的中心点为原点,但是相机的位置,也是根据世界坐标系来偏移的,webGL会将世界坐标先变换到视点坐标,然后进行裁剪,只有在视线范围(视见体)之内的场景才会进入下一阶段的计算。
这段话的意思是,首先,视点坐标,他是以相机为原点的。
而在此之前,相机的位置,就是根据最先的世界坐标系确定的。
世界坐标变换到视点坐标的意思是,我们看原点的位置,也就是屏幕上看到的画面,是建立在视点坐标,然后webGL进行裁剪后得到的。而且,只有在我们视线范围内的场景,才会进行逻辑等运算。
如下图添加了相机辅助线.
看这条白线,我们做一条垂直世界坐标平面且穿过白线的垂线,然后将原点和垂线与坐标平面相交的点相连,获得一条线,称为m。m线与白线形成的夹角,就是我们代码中初始化相机设置的角度。
而且视点坐标中的原点,就是相对于世界坐标中的坐标点,也是我们在初始化时候设置的。
同时,这条白线,是由视点坐标原点,和另一个点连接构成的。而这另一个点,也就是代码中camera.lookAt(x, y, z)中设置的。
到这里,应该就能对three.js的坐标系,和相机的位置,有一个很清晰的认识了。
若想要获取鼠标点击的物体,name就需要把屏幕坐标系转换称为three.js中的三维坐标系。
three.js提供了一个类THREE.Raycaster可以用来解决这个问题。(Raycaster:光线计算)
做了什么?THREE.Raycaster对象从屏幕上的点击位置向场景中发射一束光线。
//计算出鼠标经过的3d空间中的对象
Raycaster( origin, direction, near, far){}
origin - 射线的起点向量。
direction - 射线的方向向量,应该归一化。
near - 所有返回结果应该比 near 远。Near不能为负,默认值为0。
far - 所有返回的结果应该比 far近。 far不能小于near,默认值为无穷大。
参数先这样,具体的后面在代码中讲解。
需要知道的一点就是,new 这个对象的时候,可以不给他初始化任何值。
1 setFromCamera
用新的原点和方向来更新射线。
.setFromCamera(coords: Vector2, camera : Camera) : null
coords
- 鼠标的二维坐标,在归一化的设备坐标(NDC)中,也就是X和Y分量应该介于 -1 和 1 之间。
不需要理解太专业的属于归一化坐标,现在只需要知道,在右手坐标系中,将z轴变成1,也就是所有空间点坐标都转到了相机前单位距离处。还是不太理解,先想象成将这个坐标平面,就是面前的相机所拍摄的平面,然后这个坐标介于(-1,1)之间。注意,这里(1,-1)也是可以的。
camera
- 射线起点处的相机,即把射线起点设置在相机位置处。
2 intersectObject
来判定指定对象有没有被这束光线几种,返回被击中对象的信息,相交的结果会以一个数组的形式返回,其中的元素依照距离排序,越近的排在越前。
.intersectObject ( object, recursive : Boolean, optionalTarget : Array ) : Array
object
- 检测与射线相交的物体(只是单个物体)。
recursive
- 若为true则检查后代对象,默认值为false。
optionalTarget
- (可选参数)用来设置方法放回的设置结果。若不设置则返回一个实例化的数组,如果设置,必须在每次调用之前清楚这个数组(例如,array.length=0;)
[ { distance, point, face, faceIndex, object }, … ]
distance
- 射线的起点到相交点的距离。
point
- 在世界坐标中的交叉点。
face
- 相交的面。
faceIndex
- 相交的面的索引。
object
- 相交的对象。
uv
- 交点的二维坐标。
当计算这个对象是否和射线相交时,Raycaster把传递的对象委托给raycast方法。这允许meshes对于光线投射的响应可以不同于lines和pointclouds。
注意,对于网格,面(faces)必须朝向射线原点,这样才能被检测到;通过背面的射线的交叉点将不被检测到。为了光线投射一个对象的正反面, 你得设置material的side属性为THREE.DoubleSide
3 intersecObjects
intersectObjects 与 intersectObject 类似,除了传入的参数是一个数组之外,并无大的差别。
注意,这里是数组。
.intersectObjects ( objects : Array, recursive : Boolean, optionalTarget : Array ) : Array
objects
- 传入的参数。
关于raycaster的解释和坑:https://blog.csdn.net/u013090676/article/details/77188088
// 获取与射线相交的对象数组 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.interspectObjects()
的参数是scene.children
,因为这里是测试模型,没有涉及到外部模型的导入,但是在开发的时候我们一般都是对外部模型进行处理。
首先,你通过加载器把模型加载到场景中的时候需要在回调函数中打印一下**加载进来的是一个什么对象,**有可能是一个Mesh
或者Group
当然大部分模型资源基本上都是group但是不排除还有别的类型例如Scene
、Object
…等等。
此时,我们不能盲目的去直接把整个scene.children中的东西都放到raycaster.intersectObjects()直接进行检测,因为整个scene.children
中可能有另一个scene
或者是three.js
不能识别的对象,所以我们需要先对加载进来的对象进行处理;
最好是先创建一个组对象new THREE.Group()
,然后用这个组里面的对象来进行射线检测;
看下面方法的第一个参数Array
,Group.children
也是一个数组,所以我们可以把需要进行射线检测的物体放进一个组对象里面,便于处理;
需要注意的是:如上截图该方法的第二个参数
recursive
,若为true
,则同时也会检查所有的后代,否则将只会检查对象本身,该参数默认值为false
,如果你传入了一个scene
对象,把这个参数设置为true
,来进行后代对象检测
raycaster.intersectObjects(group.children);
元素按照距离排序,越近的越靠前
这句话的意思是,首先,点击或者触发方法创建THREE.Raycaster()
对象,然后从点击位置,发出一条射线,先被射线穿过的对象,会在数组中排序靠前。
例如我们从y轴对着球点击,然后看一下返回的数组:
返回了三个Mesh对象,因为这三个物体同时被从鼠标点击处发出的射线给穿透,因此都被返回,而求几何体离点击点近,所以第一个元素就是球体。
部分代码:
// 更新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
作用:将矢量从本地空间坐标转换为世界坐标。
注意这里,下面二维也是一样,它是在模型本身直接调用的方法,并且,这个方法(下面的二维也一样),可以不传入参数。
求出二维坐标
这里留下记号,不理解什么是逆转相机求出二维坐标。
//逆转相机求出二维坐标
let vector = object.position.clone().project(camera);
修改DIV的位置
通过求出的二维坐标,来计算位置。
left: vector.x * halfWidth + halfWidth,
top: -vector.y * halfHeight + halfHeight - object.position.y
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。