Vue+Three.js 入门五(实现流动管道效果)

LZQ plus

发布于 2020.02.29 15:17 阅读 15660 评论 0

实现流动管道的思路

   在到这里的同学应该是对Three.js的基础有了一定的认知,如果对材质不太了解的同学可以去官方网站查阅一下资料。

   言归正传,如果想要完成一个可以看见管道内部流水的效果,首先离不开的是构造函数“管道几何体(TubeGeometry)”,它可以根据三维点坐标,连接起来进而生成一个管道体,但是一个管道体想要实现上图中的效果还是不够的,具体思路就是一个管道两个管道体,外层管道体加上透明材质,内层管道体加上水的材质并设置 其材质偏移量即可实现流动效果。

实现流动管道的代码实施

在写代码之前首先认识一下管道几何体(TubeGeometry):

  TubeGeometry(path, tubularSegments, radius, radialSegments, closed)

   path — Curve - 一个由基类Curve继承而来的路径。

   tubularSegments — Integer - 组成这一管道的分段数,默认值为64。

   radius — Float - 管道的半径,默认值为1。

   radialSegments — Integer - 管道横截面的分段数目,默认值为8。

   closed — Boolean 管道的两端是否闭合,默认值为false。

结合上文,在实际开发中也是第一步,根据三维点坐标定义路径:

          posList.forEach(p => {

            v3List.push(new THREE.Vector3(p.x, p.y, p.z))

          });

          let curve = new THREE.CatmullRomCurve3(v3List, false);

然后创建一个内部流水管道体,给其添加流水材质:

              let tubeGeometry = new THREE.TubeGeometry(curve, 100, 1, 16, false);

              let material = new THREE.MeshBasicMaterial({

                map: texture,

                transparent: false,

                // opacity: 0.8,

              });

              let tube1 = new THREE.Mesh(tubeGeometry, material);

              this.scene.add(tube1);

然后创建一个外部流水管道体,给其添加透明材质:

              let tubeGeometry2 = new THREE.TubeGeometry(curve, 100, 2, 16, false);

              let tubeMaterial2 = new THREE.MeshPhongMaterial({

                color: 0xaaaaaa,

                transparent: true,

                opacity: 0.5,

              });

              let tube2 = new THREE.Mesh(tubeGeometry2, tubeMaterial2);

              this.scene.add(tube2);

需要注意的是,俩个管道体用是同一个的路径,只不过是管道半径不同而已,管道生成以后,控制内部管道材质偏移即可:

        this.textureList.forEach(t=>{

          t.offset.x -= 0.05

        });

 

下面附完整代码:

<template>

  <div class="factory-view">

    <div class="back-img">

      <img src="images/factory/back.png">

    </div>

    <div class="three-view" id="myThree"></div>

    <div class="title-view">

      <div class="title-left">

        <div class="back-btn" @click="goBack">返回</div>

      </div>

      <div class="title-center">

        <img src="images/factory/title.png"/>

        <div class="text">车间生产实时数据</div>

      </div>

      <div class="title-right">

        <span>{{nowDate | formatDate('yyyy-MM-dd hh:mm')}}</span>

      </div>

    </div>



  </div>

</template>



<script>

  import * as THREE from 'three'

  import {OBJLoader} from 'three/examples/jsm/loaders/OBJLoader'

  import {MTLLoader} from 'three/examples/jsm/loaders/MTLLoader'

  import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'



  export default {

    data() {

      return {



        nowDate: new Date().getTime(),



        container: null,

        camera: null,

        scene: null,

        renderer: null,

        controls: null,



        //用于点击事件

        raycaster: new THREE.Raycaster(),

        mouse: new THREE.Vector2(),

        //设备列表

        equipmentList: [

          {

            name: 'equipment1',

            mtl: './objs/factory/waterTank.mtl',

            obj: './objs/factory/waterTank.obj',

            position: {

              x: -100,

              y: 50,

              z: 0

            }

          },

          {

            name: 'equipment2',

            mtl: './objs/factory/waterTank.mtl',

            obj: './objs/factory/waterTank.obj',

            position: {

              x: 70,

              y: 50,

              z: 0

            }

          },

          {

            name: 'equipment3',

            mtl: './objs/factory/motor.mtl',

            obj: './objs/factory/motor.obj',

            position: {

              x: 0,

              y: -50,

              z: -5

            }

          }

        ],

        //管道列表

        pipelinePosList: [

          {

            pipelineName: 'pipeline1',

            pipelinePng: 'water1.jpg',

            posList: [

              {

                x: -50,

                y: 50,

                z: 40

              }, {

                x: -5,

                y: 50,

                z: 40

              }, {

                x: -5,

                y: -50,

                z: 40

              }, {

                x: -5,

                y: -50,

                z: 30

              }

            ]

          },

          {

            pipelineName: 'pipeline2',

            pipelinePng: 'water2.jpg',

            posList: [

              {

                x: 50,

                y: 50,

                z: 60

              }, {

                x: 10,

                y: 50,

                z: 60

              }, {

                x: 10,

                y: -50,

                z: 60

              }, {

                x: 10,

                y: -50,

                z: 30

              }

            ]

          },

          {

            pipelineName: 'pipeline3',

            pipelinePng: 'water3.jpg',

            posList: [

              {

                x: 25,

                y: -50,

                z: 21

              },

              {

                x: 170,

                y: -50,

                z: 21

              }

            ]

          }

        ],

        //管道材质,用于流动使用

        textureList: []



      }

    },

    mounted() {

      this.getNowDate();

      this.initAll();

    },

    methods: {

      /**

       * 获取当前时间

       */

      getNowDate() {

        setInterval(() => {

          this.nowDate = new Date().getTime()

        }, 60000)

      },

      initAll() {

        this.initScene();

        this.initCamera();

        this.initRender();

        this.initLight();

        this.initContent();

        window.addEventListener('resize', this.onWindowResize, false);

        this.container.addEventListener('click', this.onMouseClick, false);



        this.initControls();

        this.animate()

      },

      /**

       * 注册场景

       */

      initScene() {

        this.container = document.getElementById('myThree');

        this.scene = new THREE.Scene();

      },

      /**

       * 注册摄像机

       */

      initCamera() {

        let camera = new THREE.PerspectiveCamera(60, this.container.clientWidth / this.container.clientHeight, 1, 10000);

        camera.position.set(0, -200, 150);

        camera.lookAt(new THREE.Vector3(0, 0, 0));

        this.camera = camera;

      },

      /**

       * 注册渲染器

       */

      initRender() {

        let renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});

        renderer.setSize(this.container.clientWidth, this.container.clientHeight);

        renderer.setClearAlpha(0.5);

        this.container.appendChild(renderer.domElement);

        this.renderer = renderer;

      },

      /**

       * 注册灯光

       */

      initLight() {

        this.scene.add(new THREE.AmbientLight(0x0c0c0c));

        let spotLight1 = new THREE.SpotLight(0xdadada);

        spotLight1.position.set(-100, -300, 300);

        this.scene.add(spotLight1);

      },

      /**

       * 注册控制模块

       */

      initControls() {

        this.controls = new OrbitControls(this.camera, this.renderer.domElement);

        // 使动画循环使用时阻尼或自转 意思是否有惯性

        this.controls.enableDamping = false;

        //是否可以缩放

        this.controls.enableZoom = true;

        //是否自动旋转

        this.controls.autoRotate = false;

        //设置相机距离原点的最远距离

        this.controls.minDistance = 200;

        //设置相机距离原点的最远距离

        this.controls.maxDistance = 600;

        //是否开启右键拖拽

        this.controls.enablePan = true;

      },

      /**

       * 加载实际模型

       */

      initContent() {

        //创建一个平面(地面)

        let geometry = new THREE.PlaneGeometry(400, 200);

        let material = new THREE.MeshBasicMaterial({color: 0xaaaaaa});

        let rect = new THREE.Mesh(geometry, material);

        this.scene.add(rect);

        //添加工厂模型

        this.loadEquipment(this.equipmentList);

        //添加流动管道

        this.loadQipeLine(this.pipelinePosList);

        //添加一个参考坐标轴

        //this.scene.add(new THREE.AxesHelper(300));

      },

      /**

       * 添加交互模型

       */

      loadEquipment(list) {

        list.forEach(i => {

          let mtlLoader = new MTLLoader();

          mtlLoader.load(i.mtl, (materials) => {

            //obj的模型会和MaterialCreator包含的材质对应起来

            let objLoader = new OBJLoader();

            objLoader.setMaterials(materials);

            objLoader.load(i.obj, (obj) => {

              obj.name = i.name;

              obj.traverse(function (child) {

                if (child instanceof THREE.Mesh) {

                  child.name = i.name;

                }

              });

              obj.scale.set(1.2, 1.2, 1.2); //放大obj组对象

              obj.position.set(i.position.x, i.position.y, i.position.z);

              //绕x轴旋转π/4

              // obj.rotateZ(Math.PI/2);

              this.scene.add(obj);

              this.render();

            })

          });

        });

      },

      loadQipeLine(list) {

        list.forEach(i => {

          let v3List = [];

          i.posList.forEach(p => {

            v3List.push(new THREE.Vector3(p.x, p.y, p.z))

          });

          //1、添加管道路径

          let curve = new THREE.CatmullRomCurve3(v3List, false);

          //2、创建一个流水模型

          let tubeGeometry = new THREE.TubeGeometry(curve, 100, 1, 16, false);

          let tLoader = new THREE.TextureLoader();

          tLoader.load(`./objs/factory/${i.pipelinePng}`,

            // onLoad回调

            (texture) => {

              texture.wrapS = THREE.RepeatWrapping;

              texture.wrapT = THREE.RepeatWrapping;

              texture.repeat.x = 20;

              texture.name = i.pipelineName;

              this.textureList.push(texture);

              let material = new THREE.MeshBasicMaterial({

                map: texture,

                transparent: false,

                // opacity: 0.8,

              });

              let tube1 = new THREE.Mesh(tubeGeometry, material);

              this.scene.add(tube1);



              //3、创建一个半透明管道

              let tubeGeometry2 = new THREE.TubeGeometry(curve, 100, 2, 16, false);

              let tubeMaterial2 = new THREE.MeshPhongMaterial({

                color: 0xaaaaaa,

                transparent: true,

                opacity: 0.5,

              });

              let tube2 = new THREE.Mesh(tubeGeometry2, tubeMaterial2);

              this.scene.add(tube2);

              this.render();

            });

        });

      },

      /**

       * 配合controls使用

       */

      animate() {

        if (this.controls) {

          this.controls.update();

        }

        this.render();

        requestAnimationFrame(this.animate);

        this.textureList.forEach(t=>{

          t.offset.x -= 0.05

        });

      },

      render() {

        this.renderer.render(this.scene, this.camera);

      },



      /**

       * 监听窗口变化

       */

      onWindowResize() {

        this.camera.aspect = this.container.clientWidth / this.container.clientHeight;

        this.camera.updateProjectionMatrix();

        this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);

      },

      /**

       * 添加点击监听一系列方法

       */

      onMouseClick(event) {

        // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)

        this.mouse.x = (event.clientX / this.container.clientWidth) * 2 - 1;

        this.mouse.y = -(event.clientY / this.container.clientHeight) * 2 + 1;

        this.raycaster.setFromCamera(this.mouse, this.camera);



        // 计算物体和射线的焦点

        let intersects = this.raycaster.intersectObjects(this.scene.children, true);

        // 获取选中最近的 Mesh 对象

        if (intersects.length !== 0 && intersects[0].object instanceof THREE.Mesh) {

          // let selectName = intersects[0].object.name;

        }

      },



      goBack() {

        history.back()

      },

    }

  }

</script>



<style scoped>

  .factory-view {

    margin: 0;

    width: 100%;

    height: 100%;

    overflow: hidden;

    background: #000000;

    position: relative;

  }



  .back-img {

    position: absolute;

    top: 0;

    left: 0;

    right: 0;

    bottom: 0;

    width: 100%;

    height: 100%;

    overflow: hidden;

    display: flex;

    justify-content: center;

    align-items: center;

    z-index: 1;

  }



  .three-view {

    position: absolute;

    top: 0;

    left: 0;

    right: 0;

    bottom: 0;

    width: 100%;

    height: 100%;

    z-index: 10;

  }



  .title-view {

    position: absolute;

    top: 0;

    left: 0;

    right: 0;

    width: 100%;

    z-index: 20;

    display: flex;

    flex-wrap: wrap;

    color: #eeeeee;

  }



  .title-view .title-left {

    width: 25%;

  }



  .title-view .title-left .back-btn {

    width: 80px;

    border-radius: 0 0 5px 0;

    text-align: center;

    background: #2E3755;

    font-size: 18px;

    font-weight: bold;

    letter-spacing: 2px;

    color: #efefef;

    padding: 10px;

  }



  .title-view .title-left .back-btn:hover {

    cursor: pointer;

    color: #2E3755;

    background: #70b4de;

  }



  .title-view .title-center {

    width: 50%;

    text-align: center;

    position: relative;

  }



  .title-view .title-center img {

    width: auto;

    height: 60px;

  }



  .title-view .title-center .text {

    position: absolute;

    top: 0;

    width: 100%;

    padding-top: 10px;

    font-size: 24px;

    font-weight: bold;

    letter-spacing: 2px;

  }



  .title-view .title-right {

    width: 25%;

    padding: 15px;

    font-size: 20px;

    font-weight: bold;

    letter-spacing: 1px;

    text-align: right;

  }



</style>