效果
长按鼠标右键,旋转视角。
鼠标滚轮,缩放视角。
长按鼠标中键,平移视角。
如果你对这个模型感觉很熟悉,你肯定没看错,这是 threejs 中的 examples
https://threejs.org/examples/#webgl_animation_keyframes
咱们就是从中借鉴(CCVV)出的代码,在抄的时候,感受其中的思路与思想,本文的重点就是分享其中的要领与精髓。
在此,感谢开源者们与模型作者等人的贡献。
实现
思路
实现思路就是个球。
风在动还是树在动?长按鼠标,手指在动,模型也跟着动?
不,是心在动!
实际上是相机在动!
此控制器叫做 OrbitControls
, Orbit
是轨道的意思,轨道控制器的作用是让相机在一定的轨道上运行,就像是卫星环绕地球的轨道一样!
这个轨迹就是个球!!!相机就在这个球上做运动!
如何让相机对着目标一直拍摄呢?简单地用 lookAt
就行喽!
scope.object.lookAt(scope.target);
球坐标系
既然原理思路是个球,自然用球坐标系去算相机的位置。
此处加一个文章链接,讲述坐标系的那些事情:https://mp.weixin.qq.com/s/3vut2vfoQG6OH4OtMtZGsg
确定球坐标需要以下几点,而这些点正好对应了操作。
theta
水平方向角度,对应相机左右移动phi
竖直角度,对应相机上下移动radius
半径,对应相机与目标点的距离target
圆心坐标,对应观察点的位置修改
此处直接贴上球坐标系的代码。assets\src\math\Spherical.ts
/**
* Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system
*
* The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up.
* The azimuthal angle (theta) is measured from the positive z-axis.
*/
import { IVec3Like, math } from "cc";
class Spherical {
radius: number = 1
phi: number = 0
theta: number = 0
constructor(radius = 1, phi = 0, theta = 0) {
this.radius = radius;
this.phi = phi; // polar angle
this.theta = theta; // azimuthal angle
return this;
}
set(radius, phi, theta) {
this.radius = radius;
this.phi = phi;
this.theta = theta;
return this;
}
copy(other) {
this.radius = other.radius;
this.phi = other.phi;
this.theta = other.theta;
return this;
}
// restrict phi to be between EPS and PI-EPS
makeSafe() {
const EPS = 0.000001;
this.phi = Math.max(EPS, Math.min(Math.PI - EPS, this.phi));
return this;
}
setFromVector3(v) {
return this.setFromCartesianCoords(v.x, v.y, v.z);
}
setFromCartesianCoords(x, y, z) {
this.radius = Math.sqrt(x * x + y * y + z * z);
if (this.radius === 0) {
this.theta = 0;
this.phi = 0;
} else {
this.theta = Math.atan2(x, z);
this.phi = Math.acos(math.clamp(y / this.radius, - 1, 1));
}
return this;
}
clone() {
return new Spherical().copy(this);
}
toVec3(out: IVec3Like) {
const phi = this.phi;
const radius = this.radius;
const theta = this.theta;
const sinPhiRadius = Math.sin(phi) * radius;
out.x = sinPhiRadius * Math.sin(theta);
out.y = Math.cos(phi) * radius;
out.z = sinPhiRadius * Math.cos(theta);
return this;
}
}
export { Spherical };
再贴上同步相机的核心代码,具体逻辑可以参考下面的注释。