1、配置路径数据2、创建动画链2.1、rotationAniamte2.2、moveAnimate2.3、动画链式衔接3、动画链效果
目前数据信息可视化的发展趋势越来越快,纬度宽广、数量庞大、结构复杂的数据展示仅仅只依靠二维平面图表已经不能满足了。为了更加清晰,快速的认知和理解一份数据,构建基于现实的三维虚拟可视化效果,被广泛应用到各行业中,迅速成为信息数字化管理的重要组成部分。
三维场景还原了现实的虚拟效果,而各种各样的动画则赋予其更加饱满、灵动的视觉冲击。基于路径集合的三维动画链,它充实了场景中元素的动画效果,接下来我会具体介绍动画链的实现过程。
1、配置路径数据
此次示例中模型均采用gltf格式,以上图场景中小车为例,在模型加载完成后,我们定义配置好一系列的路径点(animatePath),让小车按照该路径执行动画。
1scene.loadGLTF('./static/gltf/car1/1.gltf', { 2 generateTangent: true, 3 useIBLWhenMissingTexture: true, 4 loadTexture: true, 5}).then((data) => { 6 const element = data.root; 7 element.scale = [0.1, 0.1, 0.1]; 8 element.ry = Math.PI; 9 element.position = [98, 1.5, -125]; 10 11 const { y } = element; 12 const animatePath = [ 13 [98, y, -135], 14 [50, y, -135], 15 [10, y, -95], 16 [-30, y, -125] 17 ] 18 19 // 创建动画链 20 element.animate = this.createPathAnimates(element, animatePath, () => { 21 // 动画链结束后的处理 22 ... ... 23 }); 24 25 element.animate.play(); 26})
其中animatePath的配置,为了满足不同的场景需求,我们可以通过各种方式去实现它的定义;
例如:
- 在场景中通过可视化的打点操作形成路径
- 在该场景元素的属性面板中,进行JSON数据的配置
- 对于精准规范的路径,可以通过接口返回的数据处理
- … …
2、创建动画链
实现小车路径集合的动画链,在遍历路径的时候,需要注意两个过程,一个是小车的moveAnimate(小车沿路径移动的动画),另一个则是每一次moveAniamte之前的rotationAniamte(小车在下一段路径动画前的朝向动画);
1createPathAnimates(element, points, done) { 2 // 声明一个有序的动画集合,方便后面进行动画链处理 3 const animates = []; 4 if (points && points.length > 0) { 5 // 获取小车的初始位置和旋转角度 6 let { x, y, z } = element; 7 let angle = element.ry; 8 for (let i = 0, len = points.length; i < len; i++) { 9 const point = points[i]; 10 const x1 = point[0]; 11 const z1 = point[2]; 12 // 计算下一段与上一段之间的角度,创建rotateAnimate 13 const rotate = Math.atan2(-(z1 - z), x1 - x); 14 const rotateAnimate = this.createRotateAnimate(element, rotate, angle); 15 if (rotateAnimate) { 16 animates.push(rotateAnimate); 17 angle = rotateAnimate.toAngle; 18 } 19 // 创建moveAnimate 20 const moveAnimate = this.createMoveAnimate(element, [x, z], [x1, z1]); 21 if (moveAnimate) { 22 animates.push(moveAnimate); 23 x = x1; 24 z = z1; 25 } 26 } 27 } 28 29 // done为动画链接结束后的回调处理函数 30 animates[animates.length - 1].onDone = done; 31 let animate; 32 for (let i = 0, len = animates.length; i < len; i++) { 33 if (i > 0) { 34 animates[i - 1].chain(animates[i]); 35 } else { 36 animate = animates[i]; 37 } 38 } 39 40 return animate; 41}
2.1、rotationAniamte
通过Math.atan2()方法,获取相对的偏移弧度:
与小车的当前弧度进行比较创建rotationAnimate:
1createRotateAnimate(element, toAngle, angle) { 2 if (toAngle !== angle) { 3 if (toAngle - angle > Math.PI) { 4 toAngle -= Math.PI * 2; 5 } 6 if (toAngle - angle < -Math.PI) { 7 toAngle += Math.PI * 2; 8 } 9 } 10 const rotateAnimate = new Animate({ 11 from: angle, 12 to: toAngle, 13 type: 'number', 14 dur: Math.abs(toAngle - angle) * 300, 15 easing: 'easeNone', 16 onPlay() { 17 element.animate = this; 18 }, 19 onUpdate(value) { 20 element.ry = value + (Math.PI / 2); 21 }, 22 }); 23 rotateAnimate.toAngle = toAngle; 24 return rotateAnimate; 25}
2.2、moveAnimate
通过上一段与下一段的point创建moveAniamte:
1createMoveAnimate(element, [x, z], [x1, z1]) { 2 return new Animate({ 3 from: [x, z], 4 to: [x1, z1], 5 type: 'point', 6 dur: Math.sqrt((x1 - x) ** 2 + (z1 - z) ** 2) * 100 || 100, 7 easing: 'easeNone', 8 onUpdate(value) { 9 const [x, z] = value; 10 element.position = vec3.fromValues(x, element.y, z); 11 }, 12 }); 13}
2.3、动画链式衔接
处理好对应的rotationAniamte和moveAnimate后,采用动画实例对象的chain方法进行链式衔接,最终的动画实力对象就能够实现我们的动画效果了,同时可以处理动画链结束后的其他操作:
1createPathAnimates(element, points, done) { 2 // 声明一个有序的动画集合,方便后面进行动画链处理 3 const animates = []; 4 ... ... 5 // done为动画链接结束后的回调处理函数 6 animates[animates.length - 1].onDone = done; 7 let animate; 8 for (let i = 0, len = animates.length; i < len; i++) { 9 if (i > 0) { 10 animates[i - 1].chain(animates[i]); 11 } else { 12 animate = animates[i]; 13 } 14 } 15 16 return animate; 17}
3、动画链效果
以上述示例来说,最终小车的动画效果:
至此,一个基于路径集合的动画链就完成了,实现的原理也并不复杂;有了这样的动画链机制,我们就可以实现以路径为核心的不同的动画效果,广泛应用到各种动画需求的场景当中。
例如,仓库中的作业流程:
1.gif
除了把路径动画链应用到场景元素上,我们还可以应用到三维场景的镜头上面,这样一来就能够实现巡航的动画效果:
2.gif