3D模块

<template>
  <div class="wrap">
    <div id="container"></div>
    <div class="toolBar">
      <el-button class="btn" type="primary" @click="resetPerspective()">{{$t('resetPerspective')}}</el-button>
      <el-button class="btn" type="primary" @click="targetPerspective()">{{enableCamera ? $t('lockPerspective') : $t('unlockPerspective')}}</el-button>
      <el-button class="btn" type="primary" @click="strabismus()">{{$t('strabismus')}}</el-button>
    </div>
  </div>
</template>

<script>
import * as THREE from 'three'
import * as OrbitControls from 'three-orbitcontrols'
import * as sceneConfig from './sceneConfig.js'
import Util3D from "./Util3D"
  export default {
    name: 'scene3D',
    props: {
      roads: {
        type: Object,
        default(){
          return {}
        }
      },
      laneStatus: {
        type: Array,
        default(){
          return []
        }
      },
      channelStatus: {
        type: Array,
        default(){
          return []
        }
      },
      roadInfo: {
        type: Array,
        default(){
          return []
        }
      },
      noCheck: {  // 是否可交互,用于区分实时状态与底图配置
        type: Boolean,
        default(){
          return false
        }
      },
      channels: {
        type: Array,
        default(){
          return []
        }
      }
    },
    methods: {
      init() {
        let container = document.getElementById('container');
        this.container = container;
        let height = container.offsetHeight;
        let width = container.offsetWidth;

        let scene = new THREE.Scene();
        window.SceneApp.scene = scene;

        //相机
        let camera = new THREE.PerspectiveCamera(45,width/height,0.01,1000);
        camera.position.set(0,60,0);
        camera.lookAt(scene.position);
        this.camera = camera;

        // 射线
        this.raycaster = new THREE.Raycaster();

        //光照
        let spotLight = new THREE.SpotLight( 0xffffff, 1, 800 );
        spotLight.position.set(-80,100,-50);
        spotLight.castShadow = true;			//生成阴影
        spotLight.shadowMapWidth = spotLight.shadowMapHeight = 512;
        scene.add(spotLight);
        let ambientLight = new THREE.AmbientLight(0xffffff,0.5);
        scene.add(ambientLight);
        
        //渲染器
        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setClearColor(0xffffff);
        this.renderer.setSize(width,height);
        this.renderer.shadowMap.enabled = true;				//渲染器渲染阴影
        this.renderer.shadowMapSoft = true;					//软阴影,即边缘虚化
        this.renderer.gammaInput = true;
        this.renderer.gammaOutput = true;
        container.appendChild(this.renderer.domElement);

        //镜头控制器
        let controller = new OrbitControls(camera, this.renderer.domElement);
        // 距离中心点最小距离和最大距离
        controller.minDistance = 10
        controller.maxDistance = 60
        // 视角的最大仰角和俯角
        controller.maxPolarAngle = Math.PI / 2.35;
        controller.enableKeys = false    //禁用键盘移动
        controller.rotateSpeed = 0.7
        sceneConfig.decoratorCtrl(controller)
        window.SceneApp.controller = controller

        // 创建方向标注
        Util3D.createDirTitle(scene)
        
        const animate = () => {
          this.timer = requestAnimationFrame(animate);
          this.renderer.render(scene, camera);
          controller.update();
        }
        animate()
      },
      // 初始化路口
      initRoad(roadType) {
        roadType = roadType || this.roadType
        let scene = window.SceneApp.scene;
        // 去重
        roadType = [...new Set(roadType)].sort((a,b) => {return a-b})

        // 删除原有的路口
        Util3D.findAllChildrenMesh().filter((v) => {
          return v.name === 'crossroad' || v.name === 'centerRoad' || v.name === 'decorations'
        }).forEach((v) => {
          this.dispose(v) // 销毁掉部分网格体,减少内存增长
          v.parent.remove(v)
        })

        // 根据路口类型,填补中间处的空白
        let shape = Util3D.getCrossroadShape(roadType)
        scene.add(Util3D.getCenterRoad(shape, roadType))
        Util3D.drawDecoration(scene, shape, roadType)

        roadType.forEach((dir) => {
          // 此处考虑加个计算路口位置的算法
          let crossroad = Util3D.getRoadMesh();
          crossroad.userData.dir = dir
          if([1,2,3,4,6,7,8,9].indexOf(dir) < 0) return;
          Util3D.setCrossroadPos(crossroad, dir, roadType);
          scene.add(crossroad);
        })

        // 画车道线
        this.drawLaneLine()

        // 更新路名
        this.updateRoadInfo()

        // 切换用户状态
        this.userState = 'normal';
      },
      // 更新车道数量
      updateRoad(mesh){
        mesh = mesh || Util3D.choosedMesh;
        if(!mesh) return;
        Util3D.findAllChildrenMesh(mesh).filter((v) => {
          return v.name === 'laneLine' || v.name === "arrow";
        }).forEach((v) => {
          this.dispose(v)
          v.parent.remove(v)
        })

        let crossroad = mesh;
        const dir = parseInt(crossroad.userData.dir);

        // 遍历路口,查询属于该路口的车道
        let laneList = this.laneList.filter((lane, i) => {
          lane.dir = parseInt(lane.dir) 
          return lane.dir === dir
        }).sort((p,c) => p.seq - c.seq)
        Util3D.drawLane(laneList, crossroad)
      },
      // 更新车道类型
      updateArrowType(type, mesh){
        mesh = mesh || Util3D.choosedMesh;
        if(mesh) mesh.updateType(type);
        if(!mesh) return;
      },
      // 画车道线
      drawLaneLine(){
        // 清空原有车道线与箭头
        Util3D.findAllChildrenMesh().filter((v) => {
          return v.name === 'laneLine' || v.name === 'arrow';
        }).forEach((v) => {
          this.dispose(v)
          v.parent.remove(v)
        })
        Util3D.findAllChildrenMesh().filter((v) => {
          return v.name === 'crossroad';
        }).forEach((m, n) => {
          let crossroad = m;
          const dir = parseInt(crossroad.userData.dir);

          // 遍历路口,查询属于该路口的车道
          let laneList = this.laneList.filter((lane, i) => {
            lane.dir = parseInt(lane.dir)
            return lane.dir === dir
          }).sort((p,c) => p.seq - c.seq)
          Util3D.drawLane(laneList, crossroad)
        })
      },
      // 画信号灯
      drawLight(){
        // 清空原有信号灯
        Util3D.findAllChildrenMesh().filter((v) => {
          return v.name === 'trafficLight';
        }).forEach((v) => {
          this.dispose(v)
          v.parent.remove(v)
        })
        Util3D.findAllChildrenMesh().filter((v) => {
          return v.name === 'crossroad';
        }).forEach((m, n) => {
          let crossroad = m;
          const dir = parseInt(crossroad.userData.dir);
          // 遍历路口,查询属于该路口的通道
          let channels = this.channels.filter((channel, i) => {
            const channelDir = this.CONST.channelDir[channel.dir] || ""
            return parseInt(channelDir.split("_")[0]) === dir
          }).sort((p,c) => p.seq - c.seq)
          Util3D.drawLight(channels, crossroad)
        })
        this.userState = 'normal';
      },
      // 更新通道类型
      updateLightType(type, mesh){
        mesh = mesh || Util3D.choosedMesh;
        if(mesh) mesh.updateType(type);
      },
      // 更新信号灯数量
      updateLights(crossroad){
        crossroad = crossroad || Util3D.choosedMesh;
        if(!crossroad) return;
        Util3D.findAllChildrenMesh(crossroad).filter((v) => {
          return v.name === 'trafficLight';  // 主杆删掉也就删掉了所含的信号灯
        }).forEach((v) => {
          this.dispose(v)
          v.parent.remove(v)
        })
        
        const dir = parseInt(crossroad.userData.dir);
        // 遍历路口,查询属于该路口的通道
        let channels = this.channels.filter((channel, i) => {
          const channelDir = this.CONST.channelDir[channel.dir] || ""
          return parseInt(channelDir.split("_")[0]) === dir
        })
        Util3D.drawLight(channels, crossroad)
        crossroad.onSelected()  // 触发一下选中操作,恢复高亮
      },
      // 重置视角
      resetPerspective() {
        window.SceneApp.controller.reset()
      },
      // 锁定/解锁视角
      targetPerspective() {
        this.enableCamera = !this.enableCamera
        window.SceneApp.controller.enabled = this.enableCamera
      },
      // 斜视视角
      strabismus() {
        window.SceneApp.controller.object.position.set(0, 16, 30)
      },
      // 实时更新车道状态
      updateStatus(list, type) {
        let meshs = Util3D.findAllChildrenMesh().filter((v) => {
          if(type === 'lane') return v.name === "arrow" || v.name === "zebra";
          else if(type === 'channel') return v.name === "light" || v.name === "passageChannel";
          else return
        })
        meshs.forEach((mesh) => {
          let id = mesh.userData && mesh.userData.id
          mesh.unHighlight()
          mesh.unFlashlight()
          let item = list.find((v) => v.id === id );
          if(item){
            switch(item.status) {
              case 1:   // 绿灯
                mesh.highlight(0x00ff00)
                break;
              case 2:   // 红灯
                mesh.highlight(0xff0000)
                break;
              case 3:   // 黄灯
                mesh.highlight(0xffff00)
                break;
              case 4:   // 绿闪
                mesh.flashlight(0x00ff00)
                break;
              case 5:   // 黄闪
                mesh.flashlight(0xffff00)
                break;
              case 6:   // 全红
                mesh.highlight(0xff0000)
                break;
              case 8:   // 红闪
                mesh.flashlight(0xff0000)
                break;
              default:
                break;
            }

            if(type === 'channel' && (item.type == 32 || item.type == 33)) {
              let laneMeshs = Util3D.findAllChildrenMesh().filter((v) => {
                return v.name === "arrow" && item.linkLaneIds.find(laneId => v.userData.id == laneId);
              })
              if(item.type == 32) { // 可变
                laneMeshs.forEach(mesh => mesh.followChannelStatus(item.status))
              }else if(item.type == 33) { // 潮汐

              }
            }
          }
        })
      },
      // 更新道路参数
      updateRoadInfo(info) {
        info = info || this.roadInfo
        let roads = Util3D.findAllChildrenMesh().filter((v) => {
          return v.name === "crossroad"
        })
        roads.forEach(road => {
          let _road = info.find(v => v.dir === road.userData.dir)
          let name = (_road && _road.roadName) || ""
          Util3D.drawRoadName(road, name)
        })
      },
      // 释放内存
      dispose(root) {
        root = root || window.SceneApp.scene
        if(root === window.SceneApp.scene) {
          if(root) {
            root.traverse((node) => {
              if(node.geometry && node.geometry.dispose) node.geometry.dispose() //释放形状
              if(node.material && node.material.dispose) node.material.dispose() //释放材质
              if(node.material && node.material.map && node.material.map.dispose) node.material.map.dispose()    // 释放贴图
              node.dispatchEvent({ type: 'dispose' });
              node = null
            });

            // 删除scene下所有的物体
            while (root.children.length > 0) {
              root.remove(root.children[0]);
            }

            this.renderer.render(root, this.camera);
            this.camera.dispatchEvent({ type: 'dispose' });
            this.camera = null
            window.SceneApp.controller.dispatchEvent({ type: 'dispose' })
            window.SceneApp.controller = null
            this.raycaster = null
            this.container.removeChild(this.renderer.domElement)
            this.container = null
            this.renderer.dispose()
            // this.renderer.forceContextLoss();
            // this.renderer.context = null;
            // this.renderer.domElement = null;
            this.renderer = null

            if(this.timer) {
              cancelAnimationFrame(this.timer)
            }
            root.dispatchEvent({ type: 'dispose' })
            root = null;
          }
        }else {
          if(!(root instanceof Array)) {
            root = [root]
            root.forEach((parent) => {
              parent.traverse((node) => {
                if(node.geometry && node.geometry.dispose) node.geometry.dispose() //释放形状
                if(node.material && node.material.dispose) node.material.dispose() //释放材质
                if(node.material && node.material.map && node.material.map.dispose) node.material.map.dispose()    // 释放贴图
                node.dispatchEvent({ type: 'dispose' });
                node = null
              })
              while (parent.children.length > 0) {
                parent.remove(parent.children[0]);
              }
            })
          }
        }
      }
    },
    data() {
      // 切勿将网格体绑定到Vue中,目前为放在window下
      return {
        renderer: null,   // 渲染器
        timer: null, 
        userState: null,  // 用户状态
        container: null,  // 场景容器
        raycaster: null,  // 射线,用于物体拾取

        laneList: [],
        roadType: [],
        enableCamera: true, //是否锁定视角
      };
    },
    computed: {
      
    },
    watch: {
      userState(val, old) {
        if(val === old || this.noCheck) return;
        let container = this.container;
        let offsetDom = document.querySelector('.stepContent') || container; // 偏移元素,因布局原因,容器的偏移计算需以此元素为准
        switch(val) {
          case 'normal':    //正常状态,可选路口和箭头
            Util3D.NoHighLight();
            // 滑过高亮
            container.onmousemove = (e) => {
              if(Util3D.choosedMesh) return   // 因为 onmousedown之后会再次触发onmousemove,故在此拦截
              _getMouse(e, this);
              Util3D.NoHighLight();
              let intersects = this.raycaster.intersectObjects(Util3D.findAllChildrenMesh().filter((v) => {
                return v.bPicked
              }).map((v) => { return [v] }).reduce((previous, current) => {
                return previous.concat(current);
              }, []));
              if(Util3D.tempObject && Util3D.tempObject.onCancelPicked) Util3D.tempObject.onCancelPicked()
              if(intersects.length) {
                let choosedMesh = intersects[0].object
                if(choosedMesh.choosedTarget) choosedMesh = choosedMesh.choosedTarget
                if(choosedMesh.onPicked) {
                  choosedMesh.onPicked()
                }
                Util3D.HighLight(choosedMesh)
              }
            }
            // 点击选中
            container.onmousedown = (e) => {
              _getMouse(e, this);
              var intersects = this.raycaster.intersectObjects(Util3D.findAllChildrenMesh().filter((v) => {
                return v.bSelected;
              }).map((v) => { return [v] }).reduce((previous, current) => {
                return previous.concat(current);
              }, []));
              if(intersects.length) {
                if(Util3D.choosedMesh && Util3D.choosedMesh.onCancelSelected) Util3D.choosedMesh.onCancelSelected()
                let choosedMesh = intersects[0].object
                if(choosedMesh.choosedTarget) choosedMesh = choosedMesh.choosedTarget
                if(choosedMesh.onSelected) choosedMesh.onSelected()
                Util3D.HighLight(choosedMesh)
                Util3D.choosedMesh = choosedMesh
                if(choosedMesh.name === "crossroad") {
                  this.userState = 'selRoad'
                  this.$emit('sel-mesh', 'selRoad', choosedMesh.userData);
                } else if(choosedMesh.name === "arrow" || choosedMesh.name === "zebra"){
                  this.userState = 'selArrow'
                  this.$emit('sel-mesh', 'selArrow', choosedMesh.userData);
                } else if(choosedMesh.name === "light" || choosedMesh.name === "passageChannel"){
                  this.userState = 'selLight'
                  this.$emit('sel-mesh', 'selLight', choosedMesh.userData);
                }
              }
            }
            break;
          default:
            container.onmousemove = null;
            break;
        }

        // 获取鼠标坐标对象,并创建射线
        function _getMouse(e, self){
          var mouse = new THREE.Vector2();  //创建二维向量,保存鼠标位置	
          e.preventDefault();  							//取消事件的默认行为,以便重新定义
          //mouse的值是(鼠标与画布中心点的距离)/(网页可见部分的大小),也就是说当鼠标在网页可见部分的中心点时,mouse.x和mouse.y都为0,他们的取值范围是(-1,1),下文中 *2-1 与 *2+1是为了判断鼠标在中心点的哪一侧
          mouse.x = ((e.clientX - offsetDom.offsetLeft) / container.clientWidth) * 2 - 1;  	// 此处需考虑滚动条位置,待修改
          mouse.y = -((e.clientY - offsetDom.offsetTop) / container.clientHeight) * 2 + 1;
          self.raycaster.setFromCamera(mouse, self.camera); 							//将当前的鼠标位置信息作为射线的起点,相机的朝向作为射线方向
        }
      },
      roads(val, old){
        if(JSON.stringify(val) === JSON.stringify(old)) return;
        let roadType = []
        let laneList = []
        for(let roadTypeNo in val){
          if([1,2,3,4,6,7,8,9].find(v => v == roadTypeNo)){ // 去除不合法方向
            roadType.push(parseInt(roadTypeNo))
            val[roadTypeNo].forEach((lane) => {
              laneList.push(lane)
            })
          }
        }
        
        this.roadType = roadType
        this.laneList = laneList
        if(JSON.stringify(Object.keys(val)) === JSON.stringify(Object.keys(old))){  // 车道数量改变
          this.updateRoad(Util3D.choosedMesh)
        }else{  // 路口类型改变 此处在选择相同路口类型时,无法重置
          this.initRoad(roadType)
          // this.drawLight()
        }
      },
      laneStatus(laneList){
        this.updateStatus(laneList, 'lane')
      },
      channelStatus(channelList){
        this.updateStatus(channelList, 'channel')
      },
      roadInfo(val, old){
        this.updateRoadInfo(val)
      },
      channels(val, old){
        if(JSON.stringify(val) === JSON.stringify(old)) return;
        let newDirs = [...new Set(val.map(channel => channel.dir))]
        let oldDirs = [...new Set(old.map(channel => channel.dir))]
        if(JSON.stringify(newDirs) === JSON.stringify(oldDirs) && 
          JSON.stringify(newDirs.map(dir => val.filter(c => c.dir == dir).length)) === JSON.stringify(oldDirs.map(dir => old.filter(c => c.dir == dir).length))) return

        // 只要所有方向不变,即视为修改通道数量
        // if(JSON.stringify([...new Set(val.map(channel => channel.dir))]) === JSON.stringify([...new Set(old.map(channel => channel.dir))])) {
        //   this.updateLights()
        // } else {
          this.drawLight()
        // }
      }
    },

    /*** 生命周期 ***/
    created(){
      window.SceneApp = {
        scene: null,
        controller: null,
      }
      // console.log('created')
    },// 此阶段编译DOM模板
    beforeMount(){
      // console.log('beforeMount')
    },// 此阶段重渲染虚拟dom
    mounted() { // 模板编译完成后执行
      // console.log('mounted')
      this.init();
    },
    beforeUpdate(){ // 仅在data中的数据变化并渲染到视图时才会触发,仅改变data不触发
      // console.log('beforeUpdate')
    },// 此阶段重渲染虚拟dom
    updated() {
      // console.log('updated')
    },
    beforeDestroy() {
      // console.log('beforeDestroy')
    },// 此阶段卸载 watcher、子组件、事件***
    destroyed() { 
      // console.log('destroyed')
      this.dispose()
    }
  };
</script>

<style scoped lang="less">
  #container, .wrap{
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
  }

  .toolBar{
    position: absolute;
    top: 15px;
    left: 15px;
  }
</style>

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务