一、前言
本文实现的是在一段圆弧上进行一段飞线进行移动,具体实现效果如下
二、思路
先绘制一段轨道圆弧,然后在绘制一段飞线圆弧,该段飞线圆弧保证开始的角度和轨道圆弧一致,然后改变飞线圆弧的姿态,让其绕着z轴旋转,当然不能一直不停的旋转,这样会脱离轨道圆弧;那么什么临界点是什么时候?因为轨道圆弧的生成从开始角度绘制到结束结束角度,那么结束角度减去开始角度就是轨道圆度的角度大小,轨道圆弧的角度减去飞线圆弧的弧度就是剩余能在轨道圆弧上旋转的角度。上图可能更直观些:
三、代码实现
理清楚思路后,进行代码实现,只展示关键代码部分,场景、相机、光源代码省略。
import * as THREE from "three";
const sumGroup = new THREE.Group();
const R = 150;
const curve = new THREE.ArcCurve(
0,
40,
R,
Math.PI / 6,
Math.PI - Math.PI / 6,
false
);
const points = curve.getPoints(100);
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints(points);
const material = new THREE.LineBasicMaterial({
color: 0x00ffff,
});
const curline = new THREE.Line(geometry, material);
// 自定义一些属性
curline.startAngle = Math.PI / 6;
curline.endAngle = Math.PI - Math.PI / 6;
sumGroup.add(curline);
// 以相同的圆心画一小段圆心 进行旋转
const flyAngle = Math.PI / 10;
const extraAngle = curline.endAngle - curline.startAngle - flyAngle; // 旋转结束角度-开始角度-飞线角度=剩余可旋转的角度
// 重新创建一个同旋转起点的小圆弧进行绕z轴旋转
const flyCurve = new THREE.ArcCurve(
0,
0,
R,
curline.startAngle,
curline.startAngle + flyAngle,
false
);
const flyPoints = flyCurve.getSpacedPoints(100);
const flyGeometry = new THREE.BufferGeometry().setFromPoints(flyPoints);
const flyMaterial = new THREE.PointsMaterial({
vertexColors: true,
size: 2.0,
});
// 创建缩小缩小系数数组
let sizeArr = [];
for (let i = 0; i < flyPoints.length; i++) {
sizeArr.push(i / flyPoints.length);
}
flyGeometry.attributes.percent = new THREE.BufferAttribute(
new Float32Array(sizeArr),
1
);
// 进行颜色插值计算
const color1 = new THREE.Color(0x006666); // 青色
const color2 = new THREE.Color(0xffff00); // 黄色
let color = null;
const colorArr = [];
const nums = flyPoints.length - 2;
for (let i = 0; i < flyPoints.length; i++) {
if (i < nums) {
color = color1.clone().lerp(color2, i / nums);
} else {
(color = color2.clone()).lerp(color1, i - nums / flyPoints.length - nums);
}
colorArr.push(color.r, color.g, color.b);
}
flyGeometry.attributes.color = new THREE.BufferAttribute(
new Float32Array(colorArr),
3
);
// 注意只有点材料才会有默认的size属性 顶点着色器才会有gl_PointSize = size 语句
flyMaterial.onBeforeCompile = (shader) => {
shader.vertexShader = shader.vertexShader.replace(
"void main() {",
["attribute float percent;", "void main() {"].join("\n")
);
shader.vertexShader = shader.vertexShader.replace(
"gl_PointSize = size",
["gl_PointSize = size * percent;"].join("\n")
);
};
const flyLine = new THREE.Points(flyGeometry, flyMaterial);
flyLine.position.y += 40;
sumGroup.add(flyLine);
function animationLoop() {
flyLine.rotation.z += 0.02;
// 当旋转的角度大于在大圆弧上可旋转的剩余角度时,说明已经不再此圆弧上旋转,需重新开始旋转
if (flyLine.rotation.z > extraAngle) flyLine.rotation.z = 0.0;
requestAnimationFrame(animationLoop);
}
animationLoop();
export default sumGroup;
四、疑问
上方的代码实现中,飞线圆弧我使用的是以原点为中心的圆弧绕其旋转,因为轨道圆弧的圆心在0,40;所以旋转完后需要进行位置移动y轴40;但是我一开始的思路是飞线圆弧以同圆心同开始旋转角度绘制,改变姿态后会不在圆弧轨道上,没弄清原因,有会的可请教!