threejs的贝塞尔曲线的生成方法不论是THREE.CubicBezierCurve3还是THREE.QuadraticBezierCurve3本质上都是 调用内部的方法计算返回曲线上的点,所以要实现多点位的贝塞尔曲线就是调用数学方法生成曲线的点。
关于贝塞尔曲线的数学公式方法网上有很多这里不赘述了,下面是整理计算好的生成方法,可以通过传入的点位数组来生成曲线上的点。
/**
* 多阶贝塞尔曲线的生成
* @param {*} anchorpoints 贝塞尔基点
* @param {*} pointsAmount 生成的点数
* @returns 路径点的Array
*/
CreateBezierPoints(anchorpoints, pointsAmount) {
// let last = anchorpoints[anchorpoints.length-1]
var points = [];
for (var i = 0; i < pointsAmount; i++) {
var point = this.MultiPointBezier(anchorpoints, i / pointsAmount);
points.push(point);
}
return points;
}
MultiPointBezier(points, t) {
var len = points.length;
var x = 0, y = 0, z = 0;
var erxiangshi = function (start, end) {
var cs = 1, bcs = 1;
while (end > 0) {
cs *= start;
bcs *= end;
start--;
end--;
}
return (cs / bcs);
};
for (var i = 0; i < len; i++) {
var point = points[i].position;
x += point.x * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));
y += point.y * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));
z += point.z * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));
}
return { x: x, y: y, z: z };
}
下面我们来调用生成这个方法来创造曲线
creatBezier() {
// 创建贝塞尔曲线对象,并添加到场景中。
// 创建控制点
const positions = [
{ x: 0, y: 0, z: 0 }, // 起点
{ x: 2, y: 4, z: 0 }, // 控制点1
{ x: 4, y: 0, z: 0 }, // 控制点2
{ x: 6, y: 0, z: 0 }, // 控制点3
{ x: 6, y: 0, z: 0 }, // 控制点4
{ x: 8, y: 4, z: 0 }, // 控制点5
{ x: 10, y: 0, z: 0 }, // 控制点6
{ x: 12, y: 0, z: 0 } // 终点
];
//这里是循环创建控制点和开始结束点,方便在页面上可视化,这个是红色小方块
for (var i = 0; i < positions.length; i++) {
var geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var sphere = new THREE.Mesh(geometry, material);
sphere.position.set(positions[i].x, positions[i].y, positions[i].z);
sphere.modelType = 1;
this.scene.add(sphere);
this.controlPoints.push(sphere);
}
//这里调用方法传入点的数组,曲线的点位切成1000份传出
this.points = this.CreateBezierPoints(this.controlPoints, 1000)
// // 创建管道
var geometry = new THREE.BufferGeometry().setFromPoints(this.points);
// 创建线条材质
var material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// 创建线条对象
this.curveObject = new THREE.Line(geometry, material);
// 将曲线对象添加到场景中,到这里已经可以在页面上看见曲线了
this.sceneHelpers.add(this.curveObject);
}
但是光看见还不够,还要可以交互,我们将曲线的更新函数加到场景的刷新函数中
// 更新贝塞尔曲线和控制点
updateBezier() {
// 更新控制点的位置
this.points = this.CreateBezierPoints(this.controlPoints, 1000)
// 重新计算曲线路径
this.curveObject.geometry.setFromPoints(this.points);
}
这样我们就可以在拖动控制点的时候,同步更新曲线的位置,弯曲程度了。
多阶贝塞尔曲线的生成本来到这里就结束了,但是threejs生成的曲线可以有封装好的方法可以直接拿到点位的切线向量,咱这个手搓的没有,没有这个方法的话就不能让模型沿着曲线方向移动,这不能忍,咱也不能少了。
// 这个是原生的贝塞尔曲线的方法 贝塞尔曲线对象 curve,并且有一个表示曲线上某个位置的参数 t
const t = 0.5; // 曲线上的位置参数,范围为 0 到 1
// 获取曲线上特定位置处的切线向量
const tangent = curve.getTangentAt(t);
// 现在 tangent 包含了曲线上特定位置处的切线向量
console.log(tangent);
//其实原理也很简单,就是取相近的2个点来计算近似的曲线
//在这里传入想要计算的点位在曲线的下标
getVectorQuantity(index) {
// 获取相邻点的位置坐标
const p1 = this.points[index - 1];//这里的this.options是之前生成的曲线1000点
const p2 = this.points[index];
// 计算相邻点之间的方向向量,作为切线向量的近似值
const tangent = new THREE.Vector3().subVectors(p2, p1).normalize();
// 现在 tangent 包含了曲线上特定点处的切线向量的近似值
console.log(tangent);
return tangent; // 返回切线向量
}
现在有了向量咱就可以让模型沿着曲线运动了
updatebox() {
//这里的点位要向下取整不然数组识别不出来
let index = Math.floor(this.time * 1000)
if (index == 0) {
index = 1
}
const pointOnCurve = this.points[index]; // 你的管道曲线坐标
this.selected.position.copy(pointOnCurve);//选中的模型
// 计算模型朝向
const tangent = this.getVectorQuantity(index);//调用计算方向的方法
this.selected.lookAt(new THREE.Vector3().addVectors(pointOnCurve, tangent));
// 更新 t,使方块沿着管道移动
this.time += 0.001; // 调整移动速度
// 如果 t 超出范围,重置为 0
if (this.time > 1) {
this.time = 0;
}
}
//将这个方法也添加到更新函数中即可