vue2 创建threejs场景
vue2 创建threejs场景
<template><div class="bgbox"><div ref="canvasContainer" class="threebox"></div></div>
</template><script>
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
export default {data() {return {scene: null,camera: null,renderer: null,controls: null,raycaster: null,objects: [],selectedObject: null,}},mounted() {if (this.$refs.canvasContainer) {this.initThreeJS()this.loadModel()this.$refs.canvasContainer.addEventListener('click', this.handleClick)}},beforeDestroy() {window.removeEventListener('resize', this.handleResize)if (this.$refs.canvasContainer) {this.$refs.canvasContainer.removeEventListener('click', this.handleClick)}// 清理Three.js资源if (this.renderer) {this.renderer.dispose()}if (this.scene) {this.scene.clear()}this.objects = []},methods: {// 窗口大小变化处理handleResize() {if (!this.renderer || !this.camera) returnconst { clientWidth, clientHeight } = this.$refs.canvasContainerthis.camera.aspect = clientWidth / clientHeightthis.camera.updateProjectionMatrix()this.renderer.setSize(clientWidth, clientHeight)},// 初始化Three.js场景initThreeJS() {// 创建场景this.scene = new THREE.Scene()// this.scene.background = new THREE.Color(0x1a1a1a);this.scene.background = new THREE.Color(0xffffff)// 创建相机this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)this.camera.position.set(-0.15310678117827767, 2.5787246941667266, -1.218985004357268)// 创建渲染器this.renderer = new THREE.WebGLRenderer({ antialias: true })this.renderer.setSize(this.$refs.canvasContainer.clientWidth, this.$refs.canvasContainer.clientHeight)this.$refs.canvasContainer.appendChild(renderer.domElement)// 添加轨道控制器this.controls = new OrbitControls(this.camera, this.renderer.domElement)this.controls.enableDamping = truethis.controls.dampingFactor = 0.1this.controls.target.set(0.24809240311931033, 0.07199656930229599, 0.06308049587088951)// this.controls.addEventListener('change', () => {// console.log('相机位置更新:', this.camera.position); // 打印当前相机位置// console.log('控制器目标:', this.controls.target); // 打印相机环绕的目标点// });// 添加光源const ambientLight = new THREE.AmbientLight(0xffffff, 0.9)this.scene.add(ambientLight)const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)directionalLight.position.set(5, 5, 5)this.scene.add(directionalLight)// 添加辅助网格const gridHelper = new THREE.GridHelper(10, 10, 0x333333, 0x222222)this.scene.add(gridHelper)// 初始化射线投射器(用于点击检测)this.raycaster = new THREE.Raycaster()// 监听窗口大小变化window.addEventListener('resize', this.handleResize)// 开始动画循环this.animate()},// 加载3D模型loadModel() {const dracoLoader = new DRACOLoader()dracoLoader.setDecoderPath('/draco/')// 创建GLTF加载器const loader = new GLTFLoader()loader.setDRACOLoader(dracoLoader)loader.load('/models/tree.glb',(gltf) => {// 处理加载完成的模型const model = gltf.scene// 调整模型大小和位置model.scale.set(0.01, 0.01, 0.01)model.position.set(0, 0, 0)// 将模型添加到场景this.scene.add(model)// 收集所有可点击的对象model.traverse((child) => {if (child.isMesh) {objects.push(child)}})},(xhr) => {},(error) => {// 加载错误console.error('模型加载错误:', error)})},// 处理点击事件handleClick(event) {if (!this.$refs.canvasContainer) return// 计算鼠标在标准化设备坐标中的位置 (-1 到 1)const rect = this.$refs.canvasContainer.getBoundingClientRect()const mouse = new THREE.Vector2(((event.clientX - rect.left) / rect.width) * 2 - 1,-((event.clientY - rect.top) / rect.height) * 2 + 1)// 更新射线投射器this.raycaster.setFromCamera(mouse, this.camera)// 检查射线与哪些对象相交const intersects = this.raycaster.intersectObjects(this.objects)// 处理相交结果if (intersects.length > 0) {const selected = intersects[0].object// 获取世界坐标const worldCoords = intersects[0].point// 将世界坐标转换为屏幕坐标const screenCoords = this.worldToScreen(worldCoords)// console.log('选中对象:', selected, screenCoords);const noNeedClickList = ['Plane033_1', 'Plane001_2', 'Plane033', 'Plane001_1', 'Plane022_1', 'Plane_1', 'Plane001_3']if (noNeedClickList.includes(selected.name)) {this.$emit('modelClick', null, { x: 0, y: 0 })} else {this.$emit('modelClick', selected, screenCoords)}} else {this.selectedObject = nullthis.$emit('modelClick', null, { x: 0, y: 0 })}},// 动画循环animate() {requestAnimationFrame(this.animate)if (this.controls) {this.controls.update()}if (this.renderer && this.scene && this.camera) {this.renderer.render(this.scene, this.camera)}},// 将世界坐标转换为屏幕坐标worldToScreen(position) {const vector = position.clone()// 将三维场景中的坐标转换为屏幕坐标vector.project(camera)return {x: Math.round(((vector.x + 1) * window.innerWidth) / 2),y: Math.round(((1 - vector.y) * window.innerHeight) / 2),}},},
}
</script><style lang="scss" scoped>
.bgbox {width: 100%;height: 100%;
}
.threebox {width: 100%;height: 100%;
}
</style>