vue+threejs 实现鼠标点击三维空间的模型点,相机平滑聚焦到对应的模型。

<template>
<div class="modelsBox">
  <div class="modelsBox_wrapper" v-if="f"></div>
  <div :id="`sign${idx + 1}`" style="position: absolute;" v-for="(v, idx) in labels" :key="idx">
    <div class="sign" :uuid="v.uuid" @click="dele">
      <div class="name">设备名称{{idx + 1}}</div>
      <div class="data">信号点1: {{parseFloat(v.x).toFixed(2)}}</div>
      <div class="data">信号点2: {{parseFloat(v.y).toFixed(2)}}</div>
      <div class="data">信号点3: {{parseFloat(v.z).toFixed(2)}}</div>
    </div>
  </div>
  <div class="opara-pannel" @click.stop>
    <div>
      <button @click="save">保存</button>
      <button @click="show">回显</button>
      <button @click="clear">还原初始化</button>
      <button @click="animate">动画</button>
    </div>
    <div>
      三维坐标点信息
      <p>{{point3d}}</p>
    </div>
  </div>
</div>
</template>

<script>

import { webglOBJ, labelTag, getPointRay, getFitScaleValue, deepCopyObject } from '@/utils/webGL/webGL.js';
import TWEEN from '@tweenjs/tween.js';
export default {
  name: 'modelsBox',
  data () {
    return {
      f: true,
      point3d: {},
      zoom: 1,
      target: '',
      sence: null,
      camera: '',
      renderer: '',
      obj: {
        sence: '',
        camera: ''
      },
      labels: [
        {x: 381.0111567102036, y: 41.66598867957452, z: -248.63694417317873},
        {x: 383.39332161333544, y: 41.37982005491592, z: -380.9167972387805},
        {x: 384.19846417704997, y: 41.50664881726524, z: -466.0620455548741}
      ]
    };
  },
  beforeDestroy () {
    document.removeEventListener('click', this.get3D);
  },
  mounted () {
    this.int();
    this.bind3Dpoint();
  },
  methods: {
    clear1 () {
      this.f = false;
      this.$nextTick(() => {
        this.f = true;
        setTimeout(() => {
          this.int();
        }, 0);
      });
    },
    clear () {
      new TWEEN.Tween(this.sence.position)
      .to({ x: 0, y: 0, z: 0}, 1000)
      .easing(TWEEN.Easing.Back.Out)
      .start();
      new TWEEN.Tween(this.camera.position)
      .to({ x: 150.00000000000006, y: 350, z: -189.99999999999997}, 1000)
      .easing(TWEEN.Easing.Back.Out)
      .start();
      new TWEEN.Tween(this.camera.rotation)
      .to({ x: -2.068139043141719, y: 0.3602177483054216, z: 2.5657085518398413}, 1000)
      .easing(TWEEN.Easing.Back.Out)
      .start();
      this.controls.update();
      this.camera.lookAt(this.sence.position);
      this.renderer.render(this.sence, this.camera);
    },
    // 点击模型获取三维坐标点的信息
    bind3Dpoint() {
      const vm = this;
      document.addEventListener('dblclick', this.get3Dmode, false);
    },

    // 点击模型还原对应视角
    get3Dmode () {
      const point3d = getPointRay(this.sence, this.camera).point;
      const time = 5000;
      // 克隆相机用户计算点击后相机聚焦的位置
      const cloneCamera = this.camera.clone();
      // this.camera.lookAt(point3d);
      cloneCamera.lookAt(point3d);

      new TWEEN.Tween(this.camera.position)
      .to({ x: cloneCamera.position.x, y: cloneCamera.position.y, z: cloneCamera.position.z}, 1000)
      .easing(TWEEN.Easing.Back.Out).start();

      new TWEEN.Tween(this.camera.rotation)
      .to({ x: cloneCamera.rotation.x, y: cloneCamera.rotation.y, z: cloneCamera.rotation.z}, 1000)
      .easing(TWEEN.Easing.Back.Out).start();
    },
    view (dir) {
      if (dir == 'top') {
        // this.sence.rotation.x = 10;
        this.renderer.render(this.sence, this.camera);
      }
    },
    show () {
      const {x, y, z} = JSON.parse(window.sessionStorage.getItem('position'));
      this.camera.position.set(x, y, z);
      this.camera.lookAt(this.target);
      this.renderer.render(this.sence, this.camera);
    },
    save () {
      // this.oCamera = deepCopyObject(this.camera);
      const {zoom, position, rotation} = this.camera;
      this.target = {...this.controls.target};
      window.sessionStorage.setItem('position', JSON.stringify(position));
      window.sessionStorage.setItem('target', JSON.stringify(this.target));
    },
    animate () {
    },
    dele () {
      // this.sence.remove(this.plane); // 删除模型
      // this.sence.add(this.plane); // 添加模型
      console.log(this.camera, this.renderer, 'this.renderer');
    },
    int () {

      const position = window.sessionStorage.getItem('position');
      const target = window.sessionStorage.getItem('target');
      let mixer = null;

      const imgBG = require('./img.jpg');
      const mtl = '/static/models/beng.mtl';
      const obj = '/static/models/beng.obj';
      const gltf = '/static/models/盒子.gltf';
      // const gltf = 'https://scqilin.github.io/learning-threejs-third/assets/models/CesiumMan/CesiumMan.gltf';

      const loader = new THREE.GLTFLoader();
      const webGLdom = document.querySelector('.modelsBox_wrapper');
      const sence = webglOBJ.createSence(webGLdom);
      const camera = webglOBJ.createCamera({
        fov: 45,
        aspect: 1,
        near: 100,
        far: 5000,
        position: {
          x: 150,
          y: 350,
          z: -190
        }
      });
      // 初始化和相机的观察的位置一样
      this.point3d = {x: 150, y: 350, z: -190};

      if (position) {
        camera.position.set(JSON.parse(position).x, JSON.parse(position).y, JSON.parse(position).z);
        camera.lookAt(JSON.parse(target));
      }

      const renderer = webglOBJ.createRenderer();
      const plane = webglOBJ.createPlane(imgBG, imgBG);
      const spotLight = webglOBJ.createSpotLight();
      const directionalLight = webglOBJ.createDirectionalLight({ x: 100000, y: 100000, z: 100000 });
      const ambient = webglOBJ.createAmbient();
      const datGui = webglOBJ.createDatGui();
      const controls = webglOBJ.createControls();
      const axisHelper = webglOBJ.createAxisHelper();

      // loader.load(gltf, function (gltf) {
      //   let object = gltf.scene;
      //   console.log(gltf, 'gltf');
      //   object.scale.set(30, 30, 30);
      //   sence.add(object);
      //   console.log(sence.getObjectByName('root'), 'root');

      //   // 动画效果
      //   mixer = new THREE.AnimationMixer(gltf.scene);
      //   console.log(gltf.animations, 'gltf.animations[i]');
      //   //同时将这个外部模型的动画全部绑定到动画混合器里面
      //   for (var i = 0; i < gltf.animations.length; i++){
          
      //     mixer.clipAction(gltf.animations[i]).play();
      //   }
      // });
      // 滚动缩小获取比例
      const vm = this;
      controls.addEventListener('change', function(evt) {
        console.log(controls.target, evt, 'zoom');
      });

      this.sence = sence;
      this.controls = controls;
      this.camera = camera;
      this.plane = plane;
      this.renderer = renderer;

      webglOBJ.loadMIT(mtl, obj, (obj) => {
        // console.log(getFitScaleValue(obj, camera), 'adasd');
      });
      // 将对象添加到场景中去
      webglOBJ.senceAdd([ directionalLight, ambient, datGui, controls, axisHelper]);
      // webglOBJ.webglRender(sence, camera, renderer);
     
     // 动画显示
     const clock = new THREE.Clock();
      function render (html) {
        vm.$nextTick(() => {
          vm.labels.forEach((val, idx) => {
            const {x, y, z} = val;
            labelTag(camera, {x,  y, z}, `sign${idx + 1}`, val, webGLdom);
          });
        });

        // 动画显示
        const time = clock.getDelta();
        if (mixer) {
          mixer.update(time);
        }

        TWEEN.update();
        renderer.render(sence, camera);
        vm.sence = sence;
        vm.camera = camera;
        requestAnimationFrame(render);
      };
      this.render = render;
      render();

    }
  }
};
</script>
<style lang="scss" scoped>
.modelsBox_wrapper {
  position: relative;
  width: 100%;
  height: 100vh;
  border: 1px solid #ccc;
  overflow: hidden;
}
.opara-pannel {
  position: absolute;
  right: 15px;
  top: 100px;
  width: 200px;
  height: 400px;
  background: rgba(0, 0, 0, 0.7);
  div, p{
    color: #fff;
  }
}
.modelsBox {
  position:relative;
  overflow: hidden;
}
div[id *= "sign"] {
  width: 250px;
  height: 100px;
  padding:10px 10px 10px 70px;
  background: rgba(0, 0, 0, .65);
  background: url('~assets/label-bg.png') center center no-repeat;
  .sign{
    div {
      color: #fff;
      text-align: left;
      padding: 0 5px;
    }
  }
}
</style>
import { startLoading } from '@/axios/index.js';
const THREE = window.THREE;
// webGL对象配置
export const webglOBJ = {
  renderDom: null,
  Scene: null, // 场景
  camera: null, // 摄像头
  renderer: null, // 渲染器
  senceAdd(objList = []) {
    objList.forEach((v) => {
      webglOBJ.Scene.add(v);
    });
  },
  // 加载模型
  loadMIT(mtl, obj, callBack) {
    const $load = startLoading();
    new THREE.MTLLoader().load(mtl, function(materials) {
      materials.preload();
      new THREE.OBJLoader().setMaterials(materials).load(
        obj,
        function(object) {
          object.traverse((child) => {
            child.castShadow = true;
            child.$msg = 'man';
            child.receiveShadow = true;
            child.position.z = 0;
            child.position.y = 0;
            child.position.x = 0;
            child.scale.x = 0.2;
            child.scale.y = 0.2;
            child.scale.z = 0.2;
          });
          webglOBJ.Scene.add(object);
        },
        function(load) {
          if (load.loaded == load.total) {
            $load.close();
          }
        }
      );
    });
  },
  // 创建场景
  createSence(renderDom) {
    this.renderDom = renderDom;
    webglOBJ.Scene = new THREE.Scene();
    webglOBJ.Scene.background = null;
    return webglOBJ.Scene;
  },
  // 创建摄像机
  createCamera({ fov, aspect, near, far, position } = {}) {
    const { width, height } = this.renderDom.getBoundingClientRect();
    let camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(position.x, position.y, position.z);
    camera.lookAt(webglOBJ.Scene.position); // 视角
    camera.enableDamping = true; // 视角
    webglOBJ.camera = camera; // 视角
    return webglOBJ.camera;
  },
  createRenderer() {
    let renderer = new THREE.WebGLRenderer();
    const { width, height } = this.renderDom.getBoundingClientRect();
    renderer.setSize(width, height);
    renderer.setClearColor(new THREE.Color(0xcccccc));
    renderer.shadowMap.enabled = true;
    this.renderDom.appendChild(renderer.domElement);
    webglOBJ.renderer = renderer;
    return webglOBJ.renderer;
  },
  createPlane(textureLoaderUrl, textureNormalUrl) {
    let planeGeometry = new THREE.PlaneGeometry(5000, 5000, 5000, 1); // 平面网格
    let textureLoader = new THREE.TextureLoader();
    let texture = textureLoader.load(textureLoaderUrl);
    let textureNormal = textureLoader.load(textureNormalUrl);
    // 加载高光贴图
    let planeMaterial = new THREE.MeshPhongMaterial({
      // specular: 0xff0000,//高光部分的颜色
      shininess: 30, //高光部分的亮度,默认30
      map: texture, // 普通纹理贴图
      roughness: 0.3,
      lightMap: textureNormal,
      // normalMap: textureNormal, //法线贴图
      bumpScale: 3,
    }); // 材质对象Material
    // let planeMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});
    let plane = new THREE.Mesh(planeGeometry, planeMaterial);
    plane.rotation.x = -0.5 * Math.PI;
    plane.position.x = 0;
    plane.name = '平面物体ID=' + 1;
    plane.position.y = 0;
    plane.position.z = -1525;
    plane.receiveShadow = true;
    return plane;
  },
  createBoxGeometry(textureLoaderUrl, { x, y, z }) {
    // 创建立方体
    let textureLoader = new THREE.TextureLoader();
    let textureNormal = textureLoader.load(textureLoaderUrl);
    let boxGeometry = new THREE.BoxGeometry(10, 10, 10, 200);
    let texture1 = textureLoader.load(textureLoaderUrl);
    let boxGeometryMaterial = new THREE.MeshLambertMaterial({
      // specular: 0xff0000,//高光部分的颜色
      shininess: 10, //高光部分的亮度,默认30
      normalScale: new THREE.Vector2(2.2, 2.2),
      map: texture1, // 普通纹理贴图
      normalMap: textureNormal, //法线贴图
      bumpMap: textureNormal,
      bumpScale: 0.3,
    });
    let box = new THREE.Mesh(boxGeometry, boxGeometryMaterial);
    box.name = '正方物体ID=' + 2;
    box.position.x = x;
    box.position.y = y;
    box.position.z = z;
    box.castShadow = true;
    return box;
  },
  // 点光源
  createSpotLight() {
    // 点光源
    let spotLight = new THREE.SpotLight(0xffffff);
    spotLight.position.set(-60, 40, -20);
    spotLight.castShadow = true;
    return spotLight;
  },
  // 平行光
  createDirectionalLight({ x, y, z }) {
    // 平行光
    let directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    // 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
    directionalLight.position.set(x, y, z);
    // 方向光指向对象网格模型mesh2,可以不设置,默认的位置是0,0,0
    // directionalLight.target = target;
    return directionalLight;
  },
  // 环境光
  createAmbient(color = 0x444444) {
    let ambient = new THREE.AmbientLight(color, 1);
    // ambient.castShadow = true;
    return ambient;
  },
  createDatGui() {
    let gui = {
      bump: 0.03,
      animation: false,
    };
    let datGui = new dat.GUI();
    //将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
    datGui.add(gui, 'bump', -1, 1).onChange(function(e) {
      box.material.bumpScale = e;
    });
    datGui.add(gui, 'animation');
    return datGui;
  },
  // 创建控制轴
  createControls() {
    let controls = new THREE.OrbitControls(webglOBJ.camera, webglOBJ.renderDom);
    return controls;
  },
  // 创建帮助
  createAxisHelper() {
    let axisHelper = new THREE.AxisHelper(250);
    return axisHelper;
  },
  // 初始化webGL对象
  webglRender(Scene, camera) {
    webglOBJ.renderer.render(Scene, camera);
    window.requestAnimationFrame(webglOBJ.webglRender);
  },
};

/**
 * 添加标签:dom方式
 * @param {*} targePosition :需要传递当前标签的位置
 * @param {*} targetId :标签对应的dom的唯一ID,暂且用时间戳代替,避免重复
 * @param {*} innerHTML :标签对应html
 */
export function labelTag(camera, targePosition, targetId, innerHTML, webGLdom) {
  const { width, height } = webGLdom.getBoundingClientRect();
  let worldVector = new THREE.Vector3(
    targePosition.x,
    targePosition.y,
    targePosition.z
  );
  let vector = worldVector.project(camera);
  let halfWidth = width / 2,
    halfHeight = height / 2;
  let x = Math.round(vector.x * halfWidth + halfWidth);
  let y = Math.round(-vector.y * halfHeight + halfHeight);
  /**
   * 更新立方体元素位置
   */
  let div = document.getElementById(targetId);
  let hg = div.getBoundingClientRect().height;
  div.style.left = x + 'px';
  div.style.top = y - hg + 'px';
  // div.innerHTML = `uuid:${innerHTML.uuid}`;
}

// 获取模型表面点的空间三维坐标
export function getPointRay(scene, camera) {
  const windowX = event.clientX; //鼠标单击位置横坐标
  const windowY = event.clientY; //鼠标单击位置纵坐标
  let res = { point: null, mesh: null };

  let x = (windowX / window.innerWidth) * 2 - 1; //标准设备横坐标
  let y = -(windowY / window.innerHeight) * 2 + 1; //标准设备纵坐标
  let standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
  //标准设备坐标转世界坐标
  let worldVector = standardVector.unproject(camera);
  let ray = worldVector.sub(camera.position).normalize();
  //创建射线投射器对象
  let raycaster = new THREE.Raycaster(camera.position, ray);
  //返回射线选中的对象
  let intersects = raycaster.intersectObjects(scene.children, true);
  console.log(intersects);
  if (intersects.length > 0) {
    let point = intersects[0].point; //射线在模型表面拾取的点坐标
    let mesh = intersects[0].object;
    res = { point, mesh, intersects };
  }
  return res;
}

// 空间坐标转二维坐标
export function transPosition(position) {
  let world_vector = new THREE.Vector3(position.x, position.y, position.z);
  let vector = world_vector.project(camera);
  let halfWidth = window.innerWidth / 2,
    halfHeight = window.innerHeight / 2;
  return {
    x: Math.round(vector.x * halfWidth + halfWidth),
    y: Math.round(-vector.y * halfHeight + halfHeight),
  };
}

// 深拷贝对象
export function deepCopyObject(obj) {
  var newObj = {};
  if (obj && typeof obj == 'object') {
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] == 'object') {
          newObj[key] = deepFn(obj[key]);
        } else {
          newObj[key] = obj[key];
        }
      }
    }
  }
  return newObj;
}

function deepCopyObject1(obj, parent = null) {
  let result = Array.isArray(obj) ? [] : {};
  let _parent = parent;
  // 该字段有父级则需要追溯该字段的父级
  while (_parent) {
    // 如果该字段引用了它的父级,则为循环引用
    if (_parent.originalParent === obj) {
      // 循环引用返回同级的新对象
      return _parent.currentParent;
    }
    _parent = _parent.parent;
  }
  if (obj && typeof obj === 'object') {
    for (let i in obj) {
      // 如果字段的值也是一个对象
      if (obj[i] && typeof obj[i] === 'object') {
        // 递归执行深拷,将同级的待拷贝对象传递给parent,方便追溯循环引用
        result[i] = deepCopyObject(obj[i], {
          originalParent: obj,
          currentParent: result,
          parent: parent,
        });
      } else {
        result[i] = obj[i];
      }
    }
  }
  return result;
}

// 点击获取对应的3d模型
export function getClick3DModle () {
  document.removeEventListener('click', clickEvent);
  document.addEventListener('click', clickEvent, false);
  // 监听点击事件查看点击的元素
  let raycaster = new THREE.Raycaster();
  let mouse = new THREE.Vector2();
  let modle = {};
  // 点击了哪个模型
  function clickEvent () {
    if (event.target.tagName == 'CANVAS') {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      sence.updateMatrixWorld(true);
      // 通过鼠标点的位置和当前相机的矩阵计算出raycaster
      raycaster.setFromCamera(mouse, camera);
      // 获取raycaster直线和所有模型相交的数组集合
      let intersects = raycaster.intersectObjects(sence.children, true);
      if (intersects[0]) {
        console.log(intersects[0]);
        modle = intersects[0];
      }
    }
  }
  return modle;
}

核心代码:克隆相机获取对应点击的三维空间的点,然后用tweenjs实现动画效果

// 点击模型还原对应视角
    get3Dmode () {
      const point3d = getPointRay(this.sence, this.camera).point;
      const time = 5000;
      // 克隆相机用户计算点击后相机聚焦的位置
      const cloneCamera = this.camera.clone();
      // this.camera.lookAt(point3d);
      cloneCamera.lookAt(point3d);

      new TWEEN.Tween(this.camera.position)
      .to({ x: cloneCamera.position.x, y: cloneCamera.position.y, z: cloneCamera.position.z}, 1000)
      .easing(TWEEN.Easing.Back.Out).start();

      new TWEEN.Tween(this.camera.rotation)
      .to({ x: cloneCamera.rotation.x, y: cloneCamera.rotation.y, z: cloneCamera.rotation.z}, 1000)
      .easing(TWEEN.Easing.Back.Out).start();
    },
// 获取模型表面点的空间三维坐标
export function getPointRay(scene, camera) {
  const windowX = event.clientX; //鼠标单击位置横坐标
  const windowY = event.clientY; //鼠标单击位置纵坐标
  let res = { point: null, mesh: null };

  let x = (windowX / window.innerWidth) * 2 - 1; //标准设备横坐标
  let y = -(windowY / window.innerHeight) * 2 + 1; //标准设备纵坐标
  let standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
  //标准设备坐标转世界坐标
  let worldVector = standardVector.unproject(camera);
  let ray = worldVector.sub(camera.position).normalize();
  //创建射线投射器对象
  let raycaster = new THREE.Raycaster(camera.position, ray);
  //返回射线选中的对象
  let intersects = raycaster.intersectObjects(scene.children, true);
  console.log(intersects);
  if (intersects.length > 0) {
    let point = intersects[0].point; //射线在模型表面拾取的点坐标
    let mesh = intersects[0].object;
    res = { point, mesh, intersects };
  }
  return res;
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值