vue使用three.js添加一个3d场景

271 阅读1分钟
<template>
    <div style="width: 100%;
    height: 100%;">
        <div id="modelBox" ref="container" style="width:800px;margin: auto;height:800px;">
        </div>
    </div>
</template>
<script>
import * as THREE from "three";
// 引入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
// // 引入模型加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import boxTextureUrl from '@/assets/box.jpg'

import rt from '@/assets/skybox/rt.png'
import lf from '@/assets/skybox/lf.png'
import up from '@/assets/skybox/up.png'
import dn from '@/assets/skybox/dn.png'
import bk from '@/assets/skybox/bk.png'
import ft from '@/assets/skybox/ft.png'
export default {
    name: "Room",
    data() {
        return {
            scene: null,
            carame: null,
            renderer: null,
            controls: null,
            gltfLoader: null,
            animation: null,
            // 时钟
            clock: null,
            // 地板
            plane: null,
            action: null
        }
    },
    mounted() {

        this.$nextTick(() => {
            this.init(),
                this.onWidowResize()
        })
    },
    methods: {
        init() {
            const contion = this.$refs.container
            // 创建场景
            this.scene = new THREE.Scene()
            // 创建相机
            this.carame = new THREE.PerspectiveCamera(70,
                contion.offsetWidth / contion.offsetHeight, 0.1, 1000)
            this.carame.position.set(0, 40, 0)
            // 创建渲染器
            this.renderer = new THREE.WebGL1Renderer()
            this.renderer.setSize(this.$refs.container.offsetWidth, this.$refs.container.offsetHeight);
            document.getElementById("modelBox").appendChild(this.renderer.domElement);
            window.addEventListener('resize', () => this.onWidowResize())
            // 动画
            this.animation = new THREE.AnimationMixer(this.scene)
            this.clock = new THREE.Clock()

            // 创建模型加载器
            this.gltfLoader = new GLTFLoader()

            /**这个错误可能是因为你的服务器没有正确地提供Soldier.glb文件,导致浏览器无法找到它。
             * 你需要确保Soldier.glb文件已经正确地上传到服务器。
             * 并且可以通过http://localhost:8080/assets/Soldier.glb这个URL来访问
              * 如果你使用的是Vue CLI来开发应用程序
              * 可以将Soldier.glb文件放在public目录下,然后在代码中使用相对路径/Soldier.glb来引用它。
              * 这是因为public目录下的文件会被直接复制到打包后的根目录下,所以可以通过根目录来访问这些文件。
             */
            this.gltfLoader.load('/Soldier.glb', gltf => {
                console.log('gltf', gltf);

                gltf.scene.name = 'Soldier';
                gltf.scene.rotation.y = Math.PI;
                // 给模型中的网格对象设置名称
                
                赋值了name值但是下面打印的时候还是空的字符串,导致点击模型的时候无法判断,暂无查到bug问题  后续补充把
                gltf.scene.traverse(child => {
                    console.log('child', child);
                    if (child.isMesh && child.name == '') {
                        child.name = 'Soldier';
                    }
                });

                // 把里面的场景添加到页面上面的场景
                this.scene.add(gltf.scene)
                // 添加环境光
                this.scene.add(new THREE.AmbientLight(0xFFFFFF, 2))

                // 把函数对象里面的动画这个是数组,找到这个走路的
                const animations = gltf.animations.find(a => a.name === "Walk")
                this.action = this.animation.clipAction(animations)
                this.action.play()
            }, undefined, (error) => {
                console.error(error)
            })



            //    页面创建这个three场景是一个canvas这个标签,这个是放在renderder里面所以点击事件监听这个rederer里面的
            /***
             *给页面中的canvas元素添加click事件监听器,当用户点击页面时,会触发该事件监听器。
             在事件监听器中,首先获取鼠标点击的坐标,并将其转换为三维坐标系中的坐标
             然后,使用Raycaster类检测哪些物体被点击,
             并过滤掉不需要响应点击事件的物体。
             最后,使用isSolider方法递归判断被点击的物体是否为士兵模型。
             */
            this.renderer.domElement.addEventListener('click', event => {
                const { offsetX, offsetY } = event
                // 3d页面比例
                const x = (offsetX / this.$refs.container.offsetWidth) * 2 - 1
                const y = (offsetY / this.$refs.container.offsetHeight) * 2 + 1
                // 鼠标点击的坐标
                const mousePoint = new THREE.Vector2(x, y)
                // 使用Raycaster类检测哪些物体被点击,
                // 并过滤掉不需要响应点击事件的物体。最后,使用isSolider方法递归判断被点击的物体是否为士兵模型。
                const raycaster = new THREE.Raycaster()
                // 设置鼠标位置和参考相机
                raycaster.setFromCamera(mousePoint, this.carame)

                // console.log('mousePoint', mousePoint);

                // console.log(' raycaster.carame', raycaster);
                console.log('raycaster', raycaster.ray.origin, raycaster.ray.direction);

                // 检测哪些物体被点击 
                // 后面参数true,是因为再创建这个glb模型的,是回调函数,然后自己又有一个舞台场景,所以true,是为了递归去判断
                const intersects = raycaster.intersectObjects(this.scene.children, true);

                console.log('intersects', intersects);
                // 把网格辅助线和平面 点击事件过滤
                // 过滤网格和地面
                // let intearrsect = intersects.filter(intersect => !(intersect.object instanceof THREE.GridHelper)
                // && intersect.object.name !== 'plane')[0];
                let intearrsect = intersects.filter(intersect => !(intersect.object instanceof THREE.GridHelper) && intersect.object.name === 'Soldier'
                )[0];

                console.log('intersect', intearrsect);
                if (intearrsect && this.isClickSoldier(intearrsect.object)) {
                    console.log(intearrsect);
                    // 停止动画
                    this.action.stop();
                    // 暂停动画
                    // this.action.paused = !this.action.paused;
                }

            })





            // 定义几何体纹理
            // const textureLoader = new THREE.TextureLoader()
            // const boxTexture = textureLoader.load(boxTextureUrl)

            // 定义形状
            // const boxGeometry = new THREE.BoxGeometry(10, 10, 10)
            // 定义材质
            // const meshBasicMaterial = new THREE.MeshBasicMaterial({
            // color: "white",
            // map: boxTexture,
            // 让立方体材质双面渲染
            // side: THREE.DoubleSide
            // })

            // 创建网格系统
            // const mesh = new THREE.Mesh(boxGeometry, meshBasicMaterial)
            // mesh.name = 'box'; // 设置对象的名称

            // const skyBoxGeometry = new THREE.BoxGeometry(200, 200, 200)
            // const skyBoxMaterials = [
            // new THREE.MeshBasicMaterial({
            // map: textureLoader.load(rt),
            // side: THREE.DoubleSide
            // }),
            // new THREE.MeshBasicMaterial({
            // map: textureLoader.load(lf),
            // side: THREE.DoubleSide
            // }),
            // new THREE.MeshBasicMaterial({
            // map: textureLoader.load(up),
            // side: THREE.DoubleSide
            // }),
            // new THREE.MeshBasicMaterial({
            // map: textureLoader.load(dn),
            // side: THREE.DoubleSide
            // }),
            // new THREE.MeshBasicMaterial({
            // map: textureLoader.load(bk),
            // side: THREE.DoubleSide
            // }),
            // new THREE.MeshBasicMaterial({
            // map: textureLoader.load(ft),
            // side: THREE.DoubleSide
            // }),
            // ]
            // const skymesh = new THREE.Mesh(skyBoxGeometry, skyBoxMaterials)
            // skymesh.name = 'boxs';
            // this.scene.add(mesh)
            // this.scene.add(skymesh)
            // 鼠标旋转
            this.controls = new OrbitControls(this.carame, this.renderer.domElement);

            // 创建一个平面
            this.plan()
            this.render()

        },
        render() {
            window.requestAnimationFrame(() => {
                this.render()
            });
            // 自动旋转
            // const box = this.scene.getObjectByName('boxs')
            // box.rotation.x += 0.01
            // box.rotation.y += 0.01


            this.animation.update(this.clock.getDelta())
            this.renderer.render(this.scene, this.carame)
        },
        onWidowResize() {
            // 相机的aspect属性,并将其设置为新的宽高比。
            this.renderer.setSize(this.$refs.container.offsetWidth, this.$refs.container.offsetHeight)
            this.carame.aspect = (this.$refs.container.offsetWidth / this.$refs.container.offsetHeight)
            this.carame.updateProjectionMatrix()

        },
        plan() {
            const geometry = new THREE.PlaneGeometry(50, 50);
            const material = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, side: THREE.DoubleSide });
            const plane = new THREE.Mesh(geometry, material);
            plane.name = 'plane'
            plane.rotation.x = -Math.PI / 2

            this.scene.add(plane);
            this.scene.add(new THREE.GridHelper(100, 100))
        },
        // 递归判断
        isSolider(object) {
            console.log('object', object);
            if (object.name === 'Solider') {
                return object
            } else if (object.parent) {
                // 判断点击的是不是有上级
                return this.isSolider(object.parent)
            } else {
                return null
            }

        }

    }
}
</script>