项目环境版本
- 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';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
export default {
props: {
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);
this.loadGLBModel();
this.$refs.threeJsContainer.appendChild(this.renderer.domElement);
const controls = new OrbitControls(this.camera, this.renderer.domElement);
controls.addEventListener('change', () => {
this.renderer.render(this.scene, this.camera);
});
},
loadGLBModel() {
const loader = new GLTFLoader();
loader.load(this.glbUrl, (gltf) => {
this.glbModel = gltf.scene;
const box = new THREE.Box3().setFromObject(gltf.scene);
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);
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) {
const vertices = new Float32Array([
min.x, max.y, min.z,
max.x, max.y, min.z,
max.x, max.y, max.z,
min.x, max.y, max.z,
min.x, min.y, min.z,
max.x, min.y, min.z,
max.x, min.y, max.z,
min.x, min.y, max.z
]);
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
]);
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>