需求:较为简单,对3D场景进行OrbitControls自带的拖动缩放功能。在自写一个点击具体3D物体拉近相机的功能,也就是flyTo()。
flyTo()是一个动画过程,(想要线上了解效果的可以参考cesium的效果,链接地址 Cesium Sandcastle,我认识这个函数名也是在cesium中)
1.引入OrbitControls,这个步骤为简单,不在详细说明。我使用的是vue项目。
//在script中引入orbitcontrols
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
//在初始化3D场景完成后创建控制对象
this.controls = new OrbitControls(this.camera,this.renderer.domElement);
至此,拖动和缩放功能应该没问题了。接下来实现flyTo()的功能。
2.flyTo()的实现。
思路:不断修改摄像头的位置和看的方向,最后聚焦于目标,涉及到 camera.position,camera.lookAt(),controls.target(不修改这个会出现一个小bug),不断修改使用tween.js。
菜全了开始动手。
// 引入,请写在正确的位置
import TWEEN from '@tweenjs/tween.js';
// 在变换之前使用一个全局变量记录过程值,
// 因为tween.js在update过程是修改参数的本身,不会返回新产生的return
// 如果只修改一个参数,例如positon,只需要将position传进去即可,
// 但是同时修改两个值,我的选择是将两个值组合在一块成为一个值
this.current = {
x:this.camera.position.x,
y:this.camera.position.y,
z:this.camera.position.z,
x1:this.controls.target.x,
y1:this.controls.target.y,
z1:this.controls.target.z
}
this.flyTo(this.getResultData(mesh))
// 计算出相机的目标位置和朝向 ,参数是被选中的物体
getResultData(mesh){
let target = {
x:mesh.position.x+300, //增加一些偏移量,使相机与目标物体之间有些距离,以便于观察
y:mesh.position.y+300,
z:mesh.position.z+150,
x1:mesh.position.x,
y1:mesh.position.y,
z1:mesh.position.z
}
return target;
},
flyTo(target){
this.tween = new TWEEN.tween(this.current);
// 这一部分一定要加上,不然flyTo动作完成后,
// 不论点击任何位置,controls都会将lootAt指向flyTo之前的位置
// 将controls 的观察目标设置为选中物体
this.tween.onComplete(()=>{
let temp = new THREE.Vector3(target.x1,target.y1,target.z1);
this.controls.target = temp;
});
// 每次更新相机位置和朝向
this.onUpdate(()={
this.camera.position = new THREE.Vector3(this.current.x,this.current.y,this.current.z);
this.camera.lootAt(this.current.x1,this.current.y1,this.current.z1);
});
this.tween.to(target,500);
this.tween.start();
},
// 在threejs的animate函数中添加
animate(){
TWEEN.update();
},
到这里功能基本完成,但是会有一个问题,就是执行controls的拖动后,在放开鼠标之前鼠标移动到了可选的mesh上,那放开拖动鼠标,会立马执行一次点击的操作,因为我flyTo()的触发事件就是click,所以算是很严重的问题。原因为拖动也是一次点击的过程。
解决思路:1、模式控制,拖动模式下取消click事件,需要flyTO()时设置controls.enable为false。2、修改controls源码,判断如果当次操作为鼠标左键拖动则取消本次的flyTo()。
// 打开 three/examples/jsm/controls/OrbitControls.js
// 在第40行增加
this.stopClick = false; // 默认是可以clcik的
this.rotateCount = 0; // 记录本次拖动的持续时间,如果持续时间较短就认为是点击
// 在252行添加
scope.rotateCount ++;
if(scope.rotateCount>3){
scope.stopClick = true;
}
// 在954行添加
if(scope.stopClick){
setTimeout(()=>{
scope.stopClick = false;
},50);
}
然后再执行flyTO()之前判断controls.stopClick是否为真,如果是则直接返回。
代码全是手打,有可能存在打错的情况,请甄别。