一、数学几何计算基础
1、角度与弧度
JavaScript语言里面用Math.PI表示180度对应的弧度值。
const angle = Math.PI/6;//30度
const angle = Math.PI/2;//90度
const angle = Math.PI;//180度three.js的数学工具类MathUtils也提供度和弧度转化的公式。
// 弧度转度
const angle = THREE.MathUtils.radToDeg(Math.PI);
console.log('Math.PI',angle);
// 度转弧度
const angle = THREE.MathUtils.degToRad(30);2、三维向量Vector3简介
向量和标量
向量就是一个有方向的量,比如在3D空间中描述一个人的速度,你需要表达速度的大小,同时也需要表达速度运动的方向,这样才能计算出3D空间中人经过一段时间,在x、y、z三个方向分别走了多长距离。
标量,就是一个没有方向的量,比如模型的位置具体坐标mesh.position。
threejs的类Vector3既可以表示向量,也可以表示标量
Vector3表示3D空间中位置坐标(非向量)
Vector3对象具有属性.x、.y、.z三个属性,这意味着你可以用Vector3对象表示3D空间中的位置坐标x、y、z。
threejs本身就会给mesh.position一个默认值THREE.Vector3(0,0,0),这就是说你可以不用给mesh.position赋值Vector3对象,你可以直接访问mesh.position,获取或设置Vector3的.x、.y、.z属性。
mesh.position.y = 80;// 设置网格模型y坐标
mesh.position.set(80,2,10);// 设置模型xyz坐标Vector3表示位移量(向量)

比如已知人在3D空间中的坐标A点是(30,30,0),此人运动到B点,从A到B的位移变化量可以用一个向量Vector3表示,已知AB在x轴上投影长度是100,y方向投影长度是50,这个变化可以用三维向量THREE.Vector3(100,50,0)表示。换句话说,你也可以理解为人沿着x轴走了100,沿着y方向走了50,到达B点。
Vector3表示速度(向量)

假设一个人的运动速度大小是√2,方向是x和y正半轴的角平分线,那么人的速度可以用向量THREE.Vector3(1, 1, 0)表示。
// 向量v表示人速度,大小√2米每秒,方向是x、y正半轴的角平分线
const v = new THREE.Vector3(1, 1, 0);以速度v运动50秒,计算运动位移变化量。
// xyz三个方向上速度分别和时间相乘,得到三个方向上位移
const walk = new THREE.Vector3(v.x * 50, v.y * 50, v.z * 50);假设人起点坐标A(30, 30, 0),以速度v运动50秒,计算运动结束位置。
const v = new THREE.Vector3(1, 1, 0);
const walk = new THREE.Vector3(v.x * 50, v.y * 50, v.z * 50);
// 运动50秒结束位置B
const B = A.clone().add(walk);向量大小(Vector3长度.length())
当向量表示位移量时,向量的长度就表示A和B两点之间的距离。
当向量表示速度时,通过向量长度方法.length()能快速计算实际速度大小
threejs的类Vector3的封装了一个方法.length(),用于计算向量长度。
向量长度.length()的内部代码,本质上就是x、y、z三个分量平方和的平方根。
const AB = B.clone().sub(A);
const L = AB.length();
console.log('L',L);.sub()表示了xyz分量分别相减,.length()表示相减结果,平方和的平方根。

3、向量运算方法
向量加法运算
.addVectors()
B.addVectors(A,walk)的含义就是向量A和向量walk的x、y、z三个分量分别相加(B.x = A.x + walk.x;、B.y = A.y + walk.y;、B.z = A.z + walk.z;)。
const A = new THREE.Vector3(30, 30, 0);// 人起点A
// walk表示运动的位移量用向量
const walk = new THREE.Vector3(100, 50, 0);
const B = new THREE.Vector3();// 人运动结束点B
// addVectors的含义就是参数中两个向量xyz三个分量分别相加
B.addVectors(A,walk);
console.log('B',B);.add()
.add()和.addVectors()功能一样,只是语法细节不同。
A和walk的x、y、z属性分别相加,相加的结果替换A原来的x、y、z。
如果不希望A被改变,且创建一个新的对象表示B点坐标,通过克隆方法.clone()。
// A.clone()克隆一个和A一样对象,然后再加上walk,作为B
// A不执行.clone(),A和B本质上都指向同一个对象
const B = A.clone().add(walk);向量复制方法.copy()
通过向量复制方法.copy(),把A和B点的坐标赋值给两个表示网格模型对象,可视化A点和B点。
// 两个小球网格模型可视化A点和B点
mesh1.position.copy(A);
mesh2.position.copy(B);向量方法.multiplyScalar()
向量方法.multiplyScalar(50)表示向量x、y、z三个分量和参数分别相乘。
v.clone().multiplyScalar(50)的含义和Vector3(v.x * 50, v.y * 50, v.z * 50)是一样的。
// `.multiplyScalar(50)`表示向量x、y、z三个分量和参数分别相乘
const walk = v.clone().multiplyScalar(50);
// 运动50秒结束位置B
const B = A.clone().add(walk);向量减法运算
.subVectors()
AB.subVectors(B,A);的含义表示B的xyz三个分量,与A的xyz三个分量分别相减,然后赋值给向量AB。
const A = new THREE.Vector3(30, 30, 0);
const B = new THREE.Vector3(130,80,0);
const AB = new THREE.Vector3();
AB.subVectors(B,A);.sub()
B.sub(A);表示B的xyz三个属性分别减去A的xyz三个属性,然后结果赋值给B自身的xyz属性
B.sub(A);
console.log('B',B);如果希望基于A和B两点位置,生成一个A指向B的向量,可以B克隆一个新对象,减去A。(如果B不克隆,B本身会被改变)
const AB = B.clone().sub(A);
console.log('AB',AB);4、向量方向(归一化.normalize)
单位向量
单位向量是向量长度.length()为1的向量。
// 单位向量v
const v = new THREE.Vector3(1,0,0);
console.log('向量长度',v.length());
// 非单位向量dir
const dir = new THREE.Vector3(1,1,0);向量归一化.normalize()
向量归一化,就是等比例缩放向量的xyz三个分量,缩放到向量长度.length()为1。
const dir = new THREE.Vector3(1, 1, 0);
dir.normalize(); //向量归一化
//Vector3(√2/2, √2/2, 0) Vector3(0.707, 0.707, 0)
console.log('dir',dir);自己写代码实现归一化。
归一化:三个分量分别除以向量长度
方法一:
const dir = new THREE.Vector3(1, 1, 0);
const L = dir.length();
// 归一化:三个分量分别除以向量长度
dir.x = dir.x / L;
dir.y = dir.y / L;
dir.z = dir.z / L;
//Vector3(√2/2, √2/2, 0) Vector3(0.707, 0.707, 0)
console.log('dir',dir);方法二:
const dir = new THREE.Vector3(1, 1, 0);
const L = v.length();
dir.multiplyScalar(1 / L);//归一化
// dir.divideScalar(L);//归一化当向量表示位移量时,单位向量用来表示位移方向。
当向量表示速度时,单位向量用来表示速度的方向。
平移方法.translateOnAxis()
//沿着AB方向平移100
mesh.translateOnAxis(AB, 100);5、应用:相机沿着视线方向运动
单位向量表示相机视线方向
相机目标观察点,也就是lookAt参数,和相机位置相减,获得一个沿着相机视线方向的向量,然后归一化,就可以获取一个表示相机视线方向的单位向量。
camera.getWorldDirection()获取相机视线方向
通过相机对象的.getWorldDirection()方法,可以快速获取一个沿着相机视线方向的单位向量,不需要自己写代码计算视线方向了,.getWorldDirection()方法进行了相关的封装。
const dir = new THREE.Vector3();
// 获取相机的视线方向
camera.getWorldDirection(dir);
console.log('相机方向',dir);
console.log('单位向量',dir.length());相机沿着视线方向平移
// dis向量表示相机沿着相机视线方向平移200的位移量
const dis = dir.clone().multiplyScalar(200);
// 相机沿着视线方向平移
camera.position.add(dis);相机沿着视线移动动画(tweenjs库辅助)
下面借助tweenjs动画库,写一个相机沿着相机视线方向移动的动画。前面基础内容中讲解过tweenjs的使用,学习下面内容之前,确保你已经熟悉tweenjs。
import TWEEN from '@tweenjs/tween.js';相机沿着视线方向移动的动画。
const dir = new THREE.Vector3();
camera.getWorldDirection(dir);// 获取相机的视线方向
// dis表示相机沿着相机视线方向平移200
const dis = dir.clone().multiplyScalar(200);
// 相机动画:平移前坐标——>平移后坐标
new TWEEN.Tween(camera.position)
.to(camera.position.clone().add(dis), 3000)
.start()
function render() {
TWEEN.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();GUI沿着相机视线方向拖动相机平移
// 从threejs扩展库引入gui.js
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
const pos0 = camera.position.clone();//记录相机初始位置
const gui = new GUI();
// L:相机沿着视线移动距离,从0~200
gui.add({L: 0}, 'L', 0, 200).onChange(function(v){
const dis = dir.clone().multiplyScalar(v);//相机沿着视线偏移长度v
const newPos = pos0.clone().add(dis);//相机初始位置+相机偏移向量
camera.position.copy(newPos);//新的位置赋值给相机位置
});6、箭头ArrowHelper
生成一个A指向B的箭头

// 绘制一个从A指向B的箭头
const AB = B.clone().sub(A);
const L = AB.length();//AB长度
const dir = AB.clone().normalize();//单位向量表示AB方向
// 生成箭头从A指向B
const arrowHelper = new THREE.ArrowHelper(dir, A, L)
group.add(arrowHelper);箭头可视化一个立方体的法线方向

const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshLambertMaterial({
color: 0x00ffff,
});
const mesh = new THREE.Mesh(geometry, material);
const p = mesh.geometry.attributes.position;
const n = mesh.geometry.attributes.normal;
const count = p.count;//顶点数量
for (let i = 0; i < count; i++) {
// 顶点位置O
const O = new THREE.Vector3(p.getX(i), p.getY(i), p.getZ(i));
// 顶点位置O对应的顶点法线
const dir = new THREE.Vector3(n.getX(i), n.getY(i), n.getZ(i));
// 箭头可视化顶点法线
const arrowHelper = new THREE.ArrowHelper(dir, O, 20);
mesh.add(arrowHelper);
}