<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>