Bootstrap

vue中加载GLB模型,计算模型的长宽高、绘制模型的边框线

项目环境版本

  • vue:2.6.12
  • threejs: 0.169.0
  • node: 16.20.0

需求背景

  • 主页面点击预览按钮,默认显示弹框,并且加载GLB模型文件
  • 点击弹框中的功能按钮,计算GLB模型的长宽高
  • 点击弹框中的功能按钮,绘制GLB模型的边框线(长方体空间)

效果预览

在这里插入图片描述

实现步骤

1.引入threejs
npm install three --save
2.创建2个Vue组件:
  • Preview.vue:预览弹框
  • LoadGlb.vue:加载Glb模型文件
3.预览弹框功能(详情见代码注释,只写关键部分)
<template>
  <div>
	<div class="top-btn">
      <div class="sizeInfo">
         <template v-if="showExtraModelInfo">
           <div><span>长:</span>{{ glbSize.x }}m</div>
           <div><span>宽:</span>{{ glbSize.y }}m</div>
           <div><span>高:</span>{{ glbSize.z }}m</div>
         </template>
       </div>

       <img class="pointer" src="@/assets/images/category.png" alt="" @click="toggleShowExtraInfo">
     </div>

     <!--    加载Glb模型      -->
     <load-glb ref="glbRef" @getSize="getSize" :glb-url="detailInfo.modelGlbPath" v-if="open" />
  </div>
<template>

<script>
import LoadGlb from "./LoadGlb.vue";
export default {
  components: {
    LoadGlb
  },
  data() {
  	glbSize: {}, // 模型的长宽高
  	showExtraModelInfo: false, // 是否显示额外的模型信息,默认不显示
  	open: false,
  },
  methods: {
	// 获取模型的长宽高
    getSize(e) {
      this.glbSize = e
    },

    // 切换显示额外的模型信息
    toggleShowExtraInfo() {
      this.showExtraModelInfo = !this.showExtraModelInfo
      this.$refs.glbRef.toggleShowOuterLine(this.showExtraModelInfo)
    }
  }
}
</script>
4.加载Glb组件(详情见代码注释)
<template>
  <div ref="threeJsContainer" class="three-js-container"></div>
</template>

<script>
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

export default {
  props: {
  	// glb模型的网络地址
    glbUrl: {
      type: String,
      required: true
    }
  },

  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      glbModel: null,
      outerLine: null, // 外侧边框线,长方体
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.initThree();
    })
  },
  methods: {
    initThree() {
      // 创建场景
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0xeeeeee);

      const width = 360; //宽度
      const height = 360; //高度

      // 创建灯光
      const ambientLight = new THREE.AmbientLight(0xffffff, 1);
      this.scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
      directionalLight.position.set(1, 1, 1);
      this.scene.add(directionalLight);
      
      // 创建相机
      this.camera = new THREE.PerspectiveCamera(45, width / height, 1, 3000);

      // 创建渲染器
      this.renderer = new THREE.WebGLRenderer();
      this.renderer.setSize(width, height);
      // 加载GLB模型
      this.loadGLBModel();
      this.$refs.threeJsContainer.appendChild(this.renderer.domElement);

      // 设置相机控件轨道控制器OrbitControls
      const controls = new OrbitControls(this.camera, this.renderer.domElement);
      // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
      controls.addEventListener('change', () => {
        this.renderer.render(this.scene, this.camera); //执行渲染操作
      });//监听鼠标、键盘事件
    },

    // 加载GLB模型
    loadGLBModel() {
      const loader = new GLTFLoader();
      loader.load(this.glbUrl, (gltf) => {
        this.glbModel = gltf.scene;
        const box = new THREE.Box3().setFromObject(gltf.scene);
		
		// 计算glb的长宽高
        const glbSize = {
          x: Number((box.max.x - box.min.x).toFixed(2)),
          y: Number((box.max.y - box.min.y).toFixed(2)),
          z: Number((box.max.z - box.min.z).toFixed(2))
        }

		// 绘制边框线,默认不显示
        this.drawBorder(box.min, box.max)

        this.$emit('getSize', glbSize)
        this.scene.add(this.glbModel);

        // 动态计算相机位置,保持中心始终看到大小相同的模型,todo: 待优化
        const { x, y, z } = glbSize
        console.log('glbSize', glbSize)
        this.camera.position.set(-x * 1.8, y * 1.4, z * 2);
        this.camera.lookAt(0, 0, 0);

        this.animate(this.renderer, this.scene, this.camera);
      });
    },

    // 画模型的边框线
    drawBorder(min, max) {
      // 定义长方体的8个顶点坐标
      const vertices = new Float32Array([
        // 顶面四个顶点
        min.x, max.y, min.z, // 0
        max.x, max.y, min.z,  // 1
        max.x, max.y, max.z,   // 2
        min.x, max.y, max.z,  // 3
        // 底面四个顶点
        min.x, min.y, min.z, // 4
        max.x, min.y, min.z,  // 5
        max.x, min.y, max.z,   // 6
        min.x, min.y, max.z   // 7
      ]);

      // 定义顶点索引,用于构成长方体的12条边
      const indices = new Uint16Array([
        0, 1, 1, 2, 2, 3, 3, 0, // 顶面边
        4, 5, 5, 6, 6, 7, 7, 4, // 底面边
        0, 4, 1, 5, 2, 6, 3, 7  // 竖直边
      ]);

      // 创建一个BufferGeometry
      const geometry = new THREE.BufferGeometry();
      geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
      geometry.setIndex(new THREE.BufferAttribute(indices, 1));

      // 创建一个材质
      const material = new THREE.LineBasicMaterial({ color: 0xfdaf58 });

      // 创建线段
      this.outerLine = new THREE.LineSegments(geometry, material);

      // 初始隐藏,点击那个玩意的时候再显示
      this.outerLine.visible = false;

      // 将线段添加到场景中
      this.scene.add(this.outerLine);
    },

    animate(renderer, scene, camera) {
      requestAnimationFrame(() => this.animate(renderer, scene, camera));
      renderer.render(scene, camera);
    },

    // 显示隐藏边框线
    toggleShowOuterLine(flag) {
      this.outerLine.visible = flag;
    }
  },
};
</script>

<style scoped>
.three-js-container {
  width: 360px;
  height: 360px;
}
</style>

;