总结Three.js中的Raycaster的使用经验
先上图:
图中的热点查询自数据库,同时鼠标点击可以添加热点
功能简介:
这个项目是按照‘党’的需求做的,党给提出的需求是:
1. 能够加载三维模型。
2. 能切换三维模型。
3. 能在模型上点击添加热点annotation。
4. 热点上可以修改标注信息。
5. 鼠标可以操作模型,包括放缩和旋转平移。
6. 能够将标注信息存到数据库,每次加载模型时不需要重新标注。
按照上面的需求,我做的过程中,遇到的主要的难点就在模型的annotation添加和保存后再加载这块了,因为之前在
Sketchfab.com网站上看到过类似的功能,(PS:老外的技术还是领先的)用起来非常的丝滑,柔顺,便利。这简单的几个功能,不知道是多少工程师的心血,但是可以肯定的是,他们能实现的,我们肯定也能实现。此处省去N多日夜……最终,如愿以偿,虽然代码写得有些冗余,但是还是基本实现了想要的功能。
Raycaster是什么?
Raycaster是Three.js中的射线,通过它,可以计算出射线与物体的交点,从而进行模型拾取,或者选中,等其他功能。
我要实现的是鼠标双击模型上的某一点,然后在这个点处添加一个annotation,然后在鼠标推拽物体,旋转物体的时候让annotation随着模型一起运动。
思路:
- 鼠标双击是要选中模型上的点的,鼠标是屏幕上的点,是二维坐标,但是模型上的点是三维坐标,如何将这个坐标进行转换呢?
- 鼠标双击事件给annotaion添加title和content。
- annotation跟随模型运动,应该是实时渲染的结果。
解决方法与实现过程:
1. 加载模型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23objLoader = new THREE.OBJLoader();
objLoader.setPath('./obj/');
objLoader.load('zxj.obj', function (object) {
object.traverse(function (child) {
if (child.type === "Mesh") {
child.geometry.computeBoundingBox();
child.geometry.verticesNeedUpdate = true;
child.material.side = THREE.DoubleSide;
}
});
object.name = "zxj"; //设置模型的名称
object.position.x = -100;//载入模型的时候的位置
object.position.y = 0;
object.position.z = -500;
// 对模型的大小进行调整
object.scale.x = 0.001;
object.scale.y = 0.001;
object.scale.z = 0.001;
//写入场景内
scene.add(object);
objects.push(object);//仿照ThreeJS写法
});
2.如何才能选中鼠标点击的点?
在Three.js中还是能找到一些例子的:Three.js
需要注意的地方:鼠标双击进行事件绑定,要监听鼠标双击事件,这里切记不要使用全局鼠标事件监听,而是应该一个一个进行绑定,否者会引起html标签中的鼠标事件失效。1
2$('#test').click(onDocumentMouseDown);
//document.addEventListener('mousedown', onDocumentMouseDown, false); // 这里是个坑啊,一定要注意 鼠标单击事件如果绑定给全局document,其他需要鼠标单击的控件,全部失效
鼠标双击事件:
鼠标获取的点是屏幕坐标,因屏幕大小而定,这里要改成设备坐标系,也就是把屏幕坐标转换成从-1到1的标准坐标系,然后使用Camera位置和Mouse点击的点来定位射线方向,通过判断射线是否经过模型来实现在模型上添加点。
我们每次鼠标双击事件就是添加一个热点,所以要将热点存储到一个数组中rays[];1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30var rays = []; //记录多次双击之后的射线与模型的外表面交点
var annos = new Array(); //热点定义成数组
var intersects; // 射线与模型的交点,这里交点会是多个,因为射线是穿过模型的,与模型的所有mesh都会有交点,但我们选取第一个,也就是intersects[0]。
function ondblClick(event) {
var clientX;
var clientY;
event.preventDefault();
//获取屏幕坐标,鼠标点击的位置的坐标
clientX = event.clientX;
clientY = event.clientY;
// 获取'转换后的设备坐标(即屏幕标准化后的坐标从 -1 到 +1):
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1; //获取鼠标点击的位置的坐标
mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);//从相机发射一条射线,经过鼠标点击位置 mouse为鼠标的二维设备坐标,camera射线起点处的相机
// intersects 是后者的返回对象数组,
intersects = raycaster.intersectObjects(objects[0].children, false); //intersects是交点数组,包含射线与mesh对象相交的所有交点
/* 如果射线与模型之间有交点,才执行如下操作 */
if (intersects.length > 0) {
if (annos.length > 20){
alert("到达热点上限");
return;
}
else {
$('#myModal').modal(); //调用模态框
//加载模态框
rays.push(intersects[0]); //将射线与模型的第一个交点push到rays数组中.
}
}
}
这样,就可以把选出来的点放入到rays[]中了。如何给这些点添加样式呢?我们需要有多少个rays交点创建多少个annotation:创建结点,append到页面,并push到annos数组中。1
2
3
4
5
6
7
8
9
10
11
12
13
14var div; // 创建anno content的div
var sp;
var strong;
var p;
var num; // annotation的数量
sp = document.createElement('p');
strong = document.createElement('strong');
p = document.createElement('p');
strong.type = 'text';
div = document.createElement('div');
div.className = 'annos';
div.style.background = 'rgba(0, 0, 0, 0.8)';
document.body.appendChild(div);
annos.push(div);
3.我们使用Ajax向后台传值,把坐标存起来
1 | $.ajax({ |
css页面:注意伪dom结点的处理
1 | .annos { |
4.更新屏幕中annotation的位置:
annotation的位置是实时渲染的,所以这个方法是对所有的annotation进行位置更新,最终应该放在render渲染器中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 function updateAnnosPosition() {
for (var i = 0; i < annos.length; i++){
var canvas = renderer.domElement;
//var vector = new THREE.Vector3(clientX,clientY,-1);
// var vector = new THREE.Vector3(intersects[i].point.x, intersects[i].point.y, intersects[i].point.z);
var vector = new THREE.Vector3(rays[i].point.x, rays[i].point.y, rays[i].point.z);
vector.project(camera);
//这个位置的写法有问题
vector.x = Math.round((0.5 + vector.x / 2) * (canvas.width / window.devicePixelRatio)); // 控制annotation跟随物体一起旋转
vector.y = Math.round((0.5 - vector.y / 2) * (canvas.height / window.devicePixelRatio));
annos[i].style.left = vector.x + "px";
annos[i].style.top = vector.y + "px";
annos[i].style.opacity = spriteBehindObject ? 0.25 : 1;
}
}
//渲染器
function render() {
renderer.render(scene, camera);
updateAnnotationOpacity(); // 修改注解的透明度
//updateScreenPosition(); // 修改注解的屏幕位置
if (annos != null){
updateAnnosPosition();
}
}
这就是整个的实现过程了,而后就是将数据存入数据库,并同样将数据库中的数据取出来重新放入一个数组 ,再将对应的x,y,z坐标复原到一个annotation中。利用render进行渲染.