Three.js自定义相机旋转动画:沿圆弧旋转

时间:2021-12-06 06:39:50

因为Tween动画封装采样点默认是在起点和终点位置的直线上采点的,这样的话给的相机的起点和终点,如果沿着直线动画移动过去,其间必然会接近物体,从而造成物体变大。

为了解决这个问题,http://*.com/questions/28091876/tween-camera-position-while-rotation-with-slerp-three-js,给出了解决方案,无奈对于四元数不太懂,而且把代码拷贝过去执行效果不佳,也不知道问题出在哪儿,所以干脆自己写个动画,即沿着圆弧移动相机。

代码也很简单,默认是沿着y轴旋转相机的,有时间抽取个函数,沿任意轴旋转相机。

        /*
*camera:相机
*angle:旋转角度
*segs:分段,即圆弧对应的路径分为几段
*during:动画执行的时间
*/

function myCameraTween(camera, angle, segs, during) {

var x = camera.position.x;
var y = camera.position.y;
var z = camera.position.z;


var endPosArray = new Array();

var perAngle = angle / segs;

for (var i = 1 ; i <= segs ; i++) {
var endPos = { "x": z * Math.sin(i * perAngle) + x * Math.cos(i * perAngle), "y": y, "z": z * Math.cos(i * perAngle) - x * Math.sin(i * perAngle) };

endPosArray.push(endPos);
}


var flag = 0;
var id = setInterval(function () {
if (flag == segs) {
clearInterval(id);
} else {
camera.position.x = endPosArray[flag].x;
camera.position.y = endPosArray[flag].y;
camera.position.z = endPosArray[flag].z;

flag++;
}

}, during / segs);
}

相机沿着相机的位置到场景原点(世界坐标系的原点)连线(下面称之为OC)的垂直向量旋转,目的是切斜观察场景中的物体:

 /*
*camera:相机
*angle:旋转角度
*segs:分段,即圆弧对应的路径分为几段
*during:动画执行的时间
*/

function myCameraTweenByAnyAxis(camera, angle, segs, during) {

var x = camera.position.x;
var y = camera.position.y;
var z = camera.position.z;

//相机向量(指向场景中心)
var v1 = new THREE.Vector3(x, y, z);

//求旋转轴,v1的垂直单位向量,令x=1,y=1,z=-(v1.x+v1.y)/v1.z
var n = (new THREE.Vector3(1, 0, -1.0 * v1.x / v1.z)).normalize();

var endPosArray = new Array();

var perAngle = angle / segs;

for (var i = 1 ; i <= segs ; i++) {
var sinDelta = Math.sin(THREE.Math.degToRad(i * perAngle));
var cosDelta = Math.cos(THREE.Math.degToRad(i * perAngle));

var tempX = x * (n.x * n.x * (1 - cosDelta) + cosDelta) + y * (n.x * n.y * (1 - cosDelta) - n.z * sinDelta) + z * (n.x * n.z * (1 - cosDelta) + n.y * sinDelta);
var tempY = x * (n.x * n.y * (1 - cosDelta) + n.z * sinDelta) + y * (n.y * n.y * (1 - cosDelta) + cosDelta) + z * (n.y * n.z * (1 - cosDelta) - n.x * sinDelta);
var tempZ = x * (n.x * n.z * (1 - cosDelta) - n.y * sinDelta) + y * (n.y * n.z * (1 - cosDelta) + n.x * sinDelta) + z * (n.z * n.z * (1 - cosDelta) + cosDelta);

var endPos = { "x": tempX, "y": tempY, "z": tempZ };

//console.log(endPos);
endPosArray.push(endPos);
}

var flag = 0;
var id = setInterval(function () {
if (flag == segs) {
clearInterval(id);
} else {
camera.position.x = endPosArray[flag].x;
camera.position.y = endPosArray[flag].y;
camera.position.z = endPosArray[flag].z;

camera.updateMatrix();

flag++;
}
}, during / segs);
}

上面的方法使用的是绕任意轴的旋转矩阵求解相机旋转路径上的各个插值点。但是有个问题就是,相机旋转到临界点,即OC与世界坐标系的夹角phi接近0 的时候,相机会突然莫名其妙绕自身y轴旋转180度,不知道是不是浮点数计算的累计误差使相机的位置计算不准确所致,所以,我们将绕任意轴旋转的旋转矩阵换成下面的方法求解相机旋转路径上的点:

/*
* r OC的距离
* phi OC与世界坐标系y轴的夹角
* theta OC在XOZ平面投影与Z轴的夹角
*/

function updateAngles(r, phi, theta) {
//var vec = new THREE.Vector3();
var x, y, z;
z = r * Math.sin(phi) * Math.cos(theta);
x = r * Math.sin(phi) * Math.sin(theta);
y = r * Math.cos(phi);

console.log("x " + x + " : " + y + " : " + z + " theta:" + theta + " phi:" + phi);
return { "x": x, "y": y, "z": z };
}