介绍
书接上文,本章主要介绍一下飞线动画的实现。
飞线主要有两部分组成:
- 连接起点和终点的一根线,这根线可以是直线,也可以是曲线,在
3d
地图中我们大概率会使用曲线,富有立体和层次感。 - 在线上运动的物体,它可以是任意
Object
,当然也可以是小姐姐做的3d
模型
实现
- 首先我们先创建一个场景,不了解的可以看我上一篇文章
typescript
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(3, 1, 3); // 设置相机位置
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// 可以使得相机围绕物体进行旋转
new OrbitControls(camera, renderer.domElement);
// 坐标系轴线
const axis = new THREE.AxesHelper();
// 将坐标系添加到场景中
scene.add(axis);
如下:
- 在
three
中一个物体的产生,需要几何信息(Geometry
)、材质(Material
)和物体(Object
),在官方提供的Geometry
中并没有可以直接拿来使用的,因此我们需要使用他们的基类BufferGeometry
来自定义几何信息,该信息存储在BufferGeometry
实例的position
属性中,属性的值是一个BufferAttribute
类实例,该类接收一个TypedArray,里面存储的是物体在三维空间中点的几何信息,因此我们需要提供的就是三维空间中组成曲线点的坐标点信息。 - 在
threejs
有一个叫做QuadraticBezierCurve3
的类,我们只需要传入起点坐标
、控制点坐标
和终点坐标
即可得到一个三维二次贝塞尔曲线,它可以提供我们所需要的点坐标信息,该类的继承了Curve(弧线)
类,我们可以通过他的实例方法getPoints
获取点的三维向量信息集合(THREE.Vector3[]
),该方法可以传递一个参数,该参数表示要将线分成多少段。
假设我们将线分成50
段、起点(-1,-1,0
)、起点(1,1,0
)、控制点(-0.5,1,1
),控制点在实际应用中可根据需要进行修改,它可以是任意值,比如说我们将控制点设置到起点和终点连线的中垂线上,它的作用主要是控制线的弯曲程度,以及弯曲转折点
typescript
const curve = new THREE.QuadraticBezierCurve3(
new THREE.Vector3(-1, -1, 0),
new THREE.Vector3(-0.5, 1, 1),
new THREE.Vector3(1, 1, 0)
);
const points = curve.getPoints(50);// 得到51个坐标点信息
点信息
看到这里有小伙伴可能会好奇为什么获取51
个点,一张图搞定
图中红色点将线分成了四段,算上红点、起点和终点一共五个点,分成更多的段以此类推,这就懂了吧!
- 有了点信息,我们就可以根据第[2]条中的描述构建几何信息了,我在这里写了三种构建方式
-
使用
BufferGeometry
实例的setFromPoints
方法,该方法接收一个三维向量数组,我们通过getPoints
方法获取的点刚好符合,可以直接使用typescriptconst geometry = new THREE.BufferGeometry().setFromPoints(points);
-
使用
setFromPoints
的本质实际是为BufferGeometry
的实例设置一个名字叫做position
的属性,因此我们可以直接调用实例的方法setAttribute
设置position
,我们得到的是三维向量坐标点信息,而TypedArray
中的是纯数字,这个也简单,我们只需要定义一个变量,然后将每一个向量的x,y,z
坐标值存入即可typescriptconst geometry = new THREE.BufferGeometry(); const dotted: number[] = []; // point为THREE.Vector3实例 points.forEach((point) => { dotted.push(point.x, point.y, point.z); }); geometry.setAttribute( "position", new THREE.BufferAttribute(new Float32Array(dotted), 3) );
-
第三种方式其实和第二种几乎一样,只不过是使用了官方提供的
Float32BufferAttribute
类,该类继承了BufferAttribute
,并将传入的值做了特殊的处理,说白了就是帮我们省略了new Float32Array(dotted)
操作typescriptconst geometry = new THREE.BufferGeometry(); const dotted: number[] = []; points.forEach((point) => { dotted.push(point.x, point.y, point.z); }); geometry.setAttribute( "position", new THREE.Float32BufferAttribute(dotted, 3) );
源码一看就懂
有小伙伴可能会说官方提供这么多,我不能用其他的吗?.....小弟浅浅尝试了,也可能我尝试的方式不对,总之没有成功(大佬指教指教🙇),就比如
Float64BufferAttribute
,直接报错,原因我在源码中看到的是这样根本就用不了....emm😰...;其他的我也有尝试使用过,要么线出不来,要么就是线的效果有实际出入,总而言之,用
Float32Array
就挺好🥳
-
- 线的几何信息出来了,加上材质和物体,添加到场景中吧
typescript
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
const curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);
效果如下
- 有了线我们该绘制在线上移动的物体了,这里就不过多赘述,我绘制的是一个圆球,并将圆球的位置设置在了线的起点
typescript
const ballGeometry = new THREE.SphereGeometry(0.03);
const ballMaterial = new THREE.MeshBasicMaterial({ color: 0xffa500 });
const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
ballMesh.position.set(-1, -1, 0);
scene.add(ballMesh);
效果:
- 球绘制出来了,可我们想要的是球沿着线移动,这里讲一下实现方法,球的移动本质就是不停的变换位置,而这个位置就是线上每一点的位置,在
Curve
类提供了一个方法getPoint
,第一个参数是弧线上的位置,值的范围值[0,1],也就是说我们可以均匀的增加第一个参数值的大小来获取不同点三维向量坐标信息。实现思路如下- 我们定义一个
rate
变量,在关键帧动画中每次增加0.0015
并对1
取余(保证rate
在[0,1]中间) - 通过将
rate
传入getPoint
中获取坐标信息 - 设置球的位置
- 我们定义一个
typescript
let rate = 0;
function animate() {
requestAnimationFrame(animate);
rate += 0.0015;
const vector = curve.getPoint(rate % 1);
ballMesh.position.set(vector.x, vector.y, vector.z);
renderer.render(scene, camera);
}
animate();
效果如下
最后,文章重在说明如何实现,实际业务就自己处理吧,另外
getPoint
方法也可以用getPointAt
代替,传入的值也是[0,1]只不过对应的是弧长,所有的代码都在步骤中体现出来了,就不贴源码了!