Vue+Three.JS加载外部obj模型初探

本文通过Vue-Cli3.0脚手架生成项目,并在项目的HelloWorld.Vue中增加3D的Demo,只是为了学习3d模型的展示与操作。

开发环境说明:node :v12.14.0;@vue/cli 4.5.8;

先来看看效果:

开始撸代码,走起!

1、安装Three
npm install three

2.接下来利用npm安装轨道控件插件:
npm install three-orbit-controls

3.接下来安装加载.obj和.mtl文件的插件:
npm i --save three-obj-mtl-loader

4.安装渲染器插件:
npm i --save three-css2drender

HelloWorld.vue文件内容

   <template>
  <div id="container" @click="mouseClick">
    <div class="moveText" :style="{ top: '0px', left: '0px' }">
      <p>机柜编号: {{ texts.code }}</p>
      <p>设备类型: {{ texts.type }}</p>
    </div>
  </div>
</template>
<script>
import * as THREE from "three";
import { OBJLoader, MTLLoader } from "three-obj-mtl-loader";
import { CSS2DRenderer, CSS2DObject } from "three-css2drender";

const OrbitControls = require("three-orbit-controls")(THREE);
export default {
  name: "vue-three",
  data() {
    return {
      texts: {
        code: "",
        type: "",
      },
      dragTop: -100,
      dragLeft: -200,
      widthBox: '',
      heightBox: '',
      cabinetData: [
        { code: '1#箱', type: '1#箱' },
        { code: '2#箱', type: '2#箱' },
        { code: '3#箱', type: '3#箱' },
        { code: '4#箱', type: '4#箱' },
        { code: '5#箱', type: '5#箱' },
        { code: '6#箱', type: '6#箱' },
        { code: '7#箱', type: '7#箱' },
        { code: '8#箱', type: '8#箱' },
        { code: '9#箱', type: '9#箱' },
        { code: '10#箱', type: '10#箱' },
      ],
      scene: "",
      light: "",
      camera: "",
      controls: "",
      renderer: "",
      
    };
  },
  methods: {
    //初始化three.js相关内容
    init() {
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0xf0f0f0);
      this.scene.add(new THREE.AmbientLight(0x0000)); //环境光
      this.light = new THREE.DirectionalLight(0x1e90ff, 1); //从正上方(不是位置)照射过来的平行光,0.45的强度
      this.light.position.set(100, 200, 100);
      this.light.position.multiplyScalar(0.3);
      this.scene.add(this.light);
      var axisHelper = new THREE.AxisHelper(20); //参数是坐标轴的长度
      this.scene.add(axisHelper);
      //初始化相机
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        1,
        1000
      );
      this.camera.position.set(-60, 30, 45);
      this.camera.lookAt(this.scene.position);
      //初始化控制器
      this.controls = new OrbitControls(this.camera);
      this.controls.target.set(0, 0, 0);
      this.controls.minDistance = 80;
      this.controls.maxDistance = 400;
      this.controls.maxPolarAngle = Math.PI / 3;
      this.controls.update();
      //渲染
      this.renderer = new THREE.WebGLRenderer({
        alpha: true,
      });
      // this.renderer.setClearColor(0x000000);
      this.renderer.setPixelRatio(window.devicePixelRatio); //为了兼容高清屏幕
      this.renderer.setSize(window.innerWidth, window.innerHeight);

      const container = document.getElementById("container");
      container.appendChild(this.renderer.domElement);
      window.addEventListener("resize", this.onWindowResize, false); //添加窗口监听事件(resize-onresize即窗口或框架被重新调整大小)
    },
    //窗口监听函数
    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    },
    animate() {
      this.controls.update();
      requestAnimationFrame(this.animate);
      this.render();
    },
    render() {
      this.renderer.render(this.scene, this.camera);
    },
    //外部模型加载函数
    loadObj() {
      let objLoader = new OBJLoader();
      let mtlLoader = new MTLLoader();
      let _this = this;
      //包含材质
      // mtlLoader.setPath("/static/models/")
      mtlLoader.load("/static/models/acm.mtl", (materials) => {
        // console.log("acm", acm);
        // materials.preload();
        objLoader.setMaterials(materials);
        objLoader.load("/static/models/acm.obj", (obj) => {
          // obj.scale.set(30, 30, 30);
          // obj.position.set(0, 0, 0);
          _this.oldChildren = _this.dealMeshMaterial(obj.children);
          obj.position.set(45, 0, 0); //模型摆放的位置
          obj.scale.set(0.01, 0.01, 0.01); //模型放大或缩小,有的时候看不到模型,考虑是不是模型太小或太大。
          this.scene.add(obj);
        });
      });
    },
    /**
     * 留住每个模型的原材质
     */
    dealMeshMaterial(arrs) {
      let result = [];
      for (let i = 0; i < arrs.length; i++) {
        let obj = {
          name: arrs[i].name,
          material: arrs[i].material,
        };
        result.push(obj);
      }
      return result;
    },
    /**
     * 用户交换事件
     */
    initControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      // 拉近摄像头
      this.controls.minDistance = 5;
      this.controls.maxDistance = 600;
      // 垂直旋转
      this.controls.maxPolarAngle = Math.PI / 2;
      this.controls.minPolarAngle = Math.PI / 4;

      this.controls.target.set(0, 5, 0);
      this.controls.autoRotate = false;
      this.controls.autoRotateSpeed = 0.5;
    },
    /**
     * 点击事件
     */
    mouseClick(event) {
      // 还原之前的 点击状态
      this.restore(this.scene.children[3].children, this.oldChildren);
      // 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
      let intersects = this.getIntersects(event);
      console.log('点击了对象:'+intersects);
      // 获取选中最近的 Mesh 对象
      if (
        intersects.length != 0 &&
        intersects[0].object instanceof THREE.Mesh
      ) {
        let selectObject = intersects[0].object;
        this.showObject(selectObject, event);
      }
    },
    /**点击事件,高亮的原理是之前先备份一份原材质,在点击之前先还原,最后通过new THREE.材质map 使用原来记录的材质map添加一个高亮颜色就可以了*/
    restore(arrsNew, arrsOld) {
      for (let i = 0; i < arrsNew.length; i++) {
        for (let j = 0; j < arrsOld.length; j++) {
          if (arrsNew[i].name === arrsOld[j].name) {
            arrsNew[i].material = arrsOld[j].material;
            break;
          }
        }
      }
    },
    /**
     * 展示点击内容
     */
    showObject(obj, event) {
      console.log(obj.name);
      let key = obj.name;
      let objs = this.cabinetData.filter((item) => {
        return item.code === key;
      });
      // 显示内容,高亮
      if (objs.length > 0) {
        let oldOneMaterial = this.oldChildren.filter(
          (item) => item.name === key
        )[0];
        obj.material = new THREE.MeshPhongMaterial({
          color: 0xfff,
          map: oldOneMaterial.material.map,
        });
        // oldOneMaterial.material.visible = false;
        //obj.material.visible = false;
        this.texts = objs[0];
        // 处理坐标
        this.dragTop = event.clientY - 50;
        this.dragLeft = event.clientX - 100;
      }
    },
    /**
     * 将屏幕坐标转换为3d 坐标
     */
    getIntersects(event) {
      var mainCanvas=event.path[0];
      event.preventDefault();
      var raycaster = new THREE.Raycaster();
      var mouse = new THREE.Vector2();
      // mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      // mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      mouse.x = ((event.clientX - mainCanvas.getBoundingClientRect().left)/mainCanvas.offsetWidth) * 2 - 1;
      mouse.y = -((event.clientY - mainCanvas.getBoundingClientRect().top)/mainCanvas.offsetHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, this.camera);
      var intersects = raycaster.intersectObjects(this.scene.children, true);
      return intersects;
    },
  },
  mounted() {
    this.init();
    this.loadObj();
    this.animate();
  },
};
</script>

<style scoped>
#container {
  width: 1200px;
  margin: 0 auto;
  height: 800px;
  overflow: hidden;
}

.text {
  position: absolute;
  font-size: 40px;
  color: #47fff8;
  left: 40px;
  bottom: 60px;
  cursor: pointer;
  animation: glow-animation 2s linear infinite alternate;
}
.moveText {
  position: absolute;
  padding: 10px;
  background: rgba(154, 200, 225, 0.3);  
}
@keyframes glow-animation {
  0% {
    font-size: 40px;
  }
  50% {
    font-size: 42px;
  }
  100% {
    font-size: 44px;
  }
}
</style>

运行:npm run dev

 

 

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值