threejs飞线动画

介绍

书接上文,本章主要介绍一下飞线动画的实现。

飞线主要有两部分组成:

  1. 连接起点和终点的一根线,这根线可以是直线,也可以是曲线,在3d地图中我们大概率会使用曲线,富有立体和层次感。
  2. 在线上运动的物体,它可以是任意Object,当然也可以是小姐姐做的3d模型

实现

  1. 首先我们先创建一个场景,不了解的可以看我上一篇文章
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);

如下:

  1. three中一个物体的产生,需要几何信息(Geometry)、材质(Material)和物体(Object),在官方提供的Geometry中并没有可以直接拿来使用的,因此我们需要使用他们的基类BufferGeometry来自定义几何信息,该信息存储在BufferGeometry实例的position属性中,属性的值是一个BufferAttribute类实例,该类接收一个TypedArray,里面存储的是物体在三维空间中点的几何信息,因此我们需要提供的就是三维空间中组成曲线点的坐标点信息。
  2. 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个点,一张图搞定

图中红色点将线分成了四段,算上红点、起点和终点一共五个点,分成更多的段以此类推,这就懂了吧!

  1. 有了点信息,我们就可以根据第[2]条中的描述构建几何信息了,我在这里写了三种构建方式
    • 使用BufferGeometry实例的setFromPoints方法,该方法接收一个三维向量数组,我们通过getPoints方法获取的点刚好符合,可以直接使用

      typescript 复制代码
      const geometry = new THREE.BufferGeometry().setFromPoints(points);
    • 使用setFromPoints的本质实际是为BufferGeometry的实例设置一个名字叫做position的属性,因此我们可以直接调用实例的方法setAttribute设置position,我们得到的是三维向量坐标点信息,而TypedArray中的是纯数字,这个也简单,我们只需要定义一个变量,然后将每一个向量的x,y,z坐标值存入即可

      typescript 复制代码
        const 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)操作

      typescript 复制代码
      const 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就挺好🥳

  2. 线的几何信息出来了,加上材质和物体,添加到场景中吧
typescript 复制代码
  const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
  const curveObject = new THREE.Line(geometry, material);

  scene.add(curveObject);

效果如下

  1. 有了线我们该绘制在线上移动的物体了,这里就不过多赘述,我绘制的是一个圆球,并将圆球的位置设置在了线的起点
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);

效果:

  1. 球绘制出来了,可我们想要的是球沿着线移动,这里讲一下实现方法,球的移动本质就是不停的变换位置,而这个位置就是线上每一点的位置,在Curve类提供了一个方法getPoint,第一个参数是弧线上的位置,值的范围值[0,1],也就是说我们可以均匀的增加第一个参数值的大小来获取不同点三维向量坐标信息。实现思路如下
    1. 我们定义一个rate变量,在关键帧动画中每次增加0.0015并对1取余(保证rate在[0,1]中间)
    2. 通过将rate传入getPoint中获取坐标信息
    3. 设置球的位置
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]只不过对应的是弧长,所有的代码都在步骤中体现出来了,就不贴源码了!

相关推荐
熊的猫12 分钟前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
四喜花露水1 小时前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie2 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
工业甲酰苯胺2 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
程序员爱技术5 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
悦涵仙子6 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜6 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
cs_dn_Jie10 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic11 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js