上源代码:主流程代码AboutView.vue
<template>
<div class="current-page">
<canvas id="draw" `class`="draw" style="border: 1px solid; background-color: #000"></canvas>
</div>
</template>
<script setup lang="ts">
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as THREE from 'three'
import { onMounted, onUnmounted } from 'vue'
import grassUrl from '@/assets/img/grass.jpg'
import floorUrl from '@/assets/img/floor.jpeg'
import skyUrl from '@/assets/img/sky.jpeg'
import { getThreeControls } from './interactiveControls.ts'
import { getHoursData } from './intehours.ts'
// Three.js相关对象
let { scene, camera, renderer, canvas, group, doorMesh, doorGroup, marker, handleDoorClick } =
getThreeControls()
let { houseSize, housePosition, housePositiontwo } = getHoursData()
// 新增:射线投射器和指针变量
const raycaster = new THREE.Raycaster()
const pointer = new THREE.Vector2()
// 尺寸常量
const width = 1200
const height = 800
// 创建一个小木屋模型mui 的模型
function inithouse(sizeData, positionData) {
createFloor(sizeData, positionData)
createWalls(sizeData, positionData)
createNoDoorWall(sizeData, positionData)
createDoorWall(sizeData, positionData)
createRoof(sizeData, positionData)
createDoor(sizeData, positionData) // 新增创建门
}
function initThree() {
// 修复:使用全局canvas变量,不使用const重新声明
canvas = document.querySelector('#draw') as HTMLCanvasElement
// 创建场景
scene = new THREE.Scene()
// 创建相机
camera = new THREE.PerspectiveCamera(125, width / height, 1, 2000)
camera.position.set(50, 10, 50)
// 添加环境光
const hjLight = new THREE.AmbientLight(0xffffff)
scene.add(hjLight)
scene.add(marker)
// 添加建筑组
scene.add(group)
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true,
})
renderer.setSize(width, height)
// 添加轨道控制器
const controls = new OrbitControls(camera, canvas)
controls.addEventListener('change', () => {
if (renderer && scene && camera) {
renderer.render(scene, camera)
}
})
// 初始渲染
renderer.render(scene, camera)
}
// 创建地面
function createGround() {
if (!scene) return
const groundTexture = new THREE.TextureLoader().load(grassUrl)
groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping
groundTexture.repeat.set(100, 100)
const ground = new THREE.CircleGeometry(500, 100)
const groundMaterial = new THREE.MeshLambertMaterial({
side: THREE.DoubleSide,
map: groundTexture,
})
const groundMesh = new THREE.Mesh(ground, groundMaterial)
groundMesh.name = 'ground'
groundMesh.rotateX(-Math.PI / 2)
scene.add(groundMesh)
}
// 创建地板
function createFloor(sizeData, PositionData) {
if (!group) return
const texture = new THREE.TextureLoader().load(floorUrl)
const floor = new THREE.BoxGeometry(sizeData.baseWidth, 1, sizeData.baseLength)
const material = new THREE.MeshPhongMaterial({ map: texture })
const mesh = new THREE.Mesh(floor, material)
mesh.position.set(PositionData.x, PositionData.y, PositionData.z)
mesh.name = 'floor'
group.add(mesh)
}
function createWall(width, height, thickness, imgUrl) {
const wallTexture = new THREE.TextureLoader().load(imgUrl)
const wall = new THREE.BoxGeometry(width, height, thickness)
const material = new THREE.MeshPhongMaterial({ map: wallTexture })
return new THREE.Mesh(wall, material)
}
function createWalls(sizeData, PositionData) {
if (!group) return
// 左侧墙
const leftWall = createWall(
sizeData.baseWidth,
sizeData.baseHeight,
sizeData.baseThickness,
sizeData.baseImg,
)
leftWall.name = 'leftWall'
leftWall.position.set(PositionData.x, 10, sizeData.baseLength / 2 + PositionData.z)
group.add(leftWall)
// 右侧墙
const rightWall = createWall(
sizeData.baseWidth,
sizeData.baseHeight,
sizeData.baseThickness,
sizeData.baseImg,
)
rightWall.name = 'rightWall'
rightWall.position.set(PositionData.x, 10, -sizeData.baseLength / 2 + PositionData.z)
group.add(rightWall)
}
function genwallShapeNo(sizeData) {
const shape = new THREE.Shape()
let height = houseSize.baseHeight // 使用统一的高度
// 绘制墙体轮廓
shape.moveTo(0, 0)
shape.lineTo(0, height)
shape.lineTo(
sizeData.baseLength / 2 - sizeData.peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight - 1,
)
shape.lineTo(
sizeData.baseLength / 2 - sizeData.peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight,
)
shape.lineTo(
sizeData.baseLength / 2 + sizeData.peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight,
)
shape.lineTo(
sizeData.baseLength / 2 + sizeData.peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight - 1,
)
shape.lineTo(sizeData.baseLength, sizeData.baseHeight)
shape.lineTo(sizeData.baseLength, 0)
shape.lineTo(0, 0)
return shape
}
function createIrregularWall(shape, position, textureUrl) {
const extrudeSettings = {
depth: houseSize.baseThickness, // 使用统一的厚度
bevelEnabled: false,
}
const wallTexture = new THREE.TextureLoader().load(textureUrl)
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping
wallTexture.repeat.set(0.05, 0.05)
const material = new THREE.MeshPhongMaterial({
map: wallTexture,
side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(geometry, material)
mesh.position.set(position[0], position[1], position[2])
group.add(mesh)
return mesh
}
function createNoDoorWall(sizeData, positionData) {
const shape = genwallShapeNo(sizeData)
let mesh = createIrregularWall(
shape,
[
-sizeData.baseWidth / 2 + positionData.x,
0 + positionData.y,
sizeData.baseLength / 2 + positionData.z,
],
sizeData.baseImg,
)
mesh.name = 'backWall'
mesh.rotation.y = Math.PI / 2
}
function createDoorWall(sizeData, positionData) {
const shape = genwallShapeNo(sizeData)
const door = new THREE.Path()
const doorOffsetX = sizeData.baseLength / 2
door.moveTo(doorOffsetX - sizeData.doorWidth / 2, 0)
door.lineTo(doorOffsetX - sizeData.doorWidth / 2, sizeData.doorHeight)
door.lineTo(doorOffsetX + sizeData.doorWidth / 2, sizeData.doorHeight)
door.lineTo(doorOffsetX + sizeData.doorWidth / 2, 0)
shape.holes.push(door)
const mesh = createIrregularWall(
shape,
[
sizeData.baseWidth / 2 + positionData.x,
0 + positionData.y,
sizeData.baseLength / 2 + positionData.z,
],
sizeData.baseImg,
)
mesh.name = 'doorWall'
mesh.rotation.y = Math.PI / 2
}
function createRoof(sizeData, positionData) {
if (!group) return
// 屋顶参数
const roofPeakHeight = sizeData.peakHeight // 屋顶顶点高度
const roofEaveLength = 5 // 屋檐延伸长度
// 计算屋顶实际尺寸
const roofWidth = Math.sqrt(sizeData.baseWidth ** 2 + roofPeakHeight ** 2) + roofEaveLength
const roofLength = sizeData.baseWidth - 6 // 比墙面略长
const roofThickness = 1 // 屋顶厚度
// 屋顶高度位置 (墙高 + 屋顶厚度)
const roofPositionY = sizeData.baseHeight + roofThickness + 1
// 创建屋顶几何体
const geometry = new THREE.BoxGeometry(roofLength, roofWidth, roofThickness)
// 加载屋顶纹理
const texture = new THREE.TextureLoader().load(sizeData.tileUrl)
texture.wrapS = texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(2, 2)
// 创建屋顶材质
const material = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
})
// 创建右侧屋顶
const rightRoof = new THREE.Mesh(geometry, material)
rightRoof.rotation.set(
80 * (Math.PI / 180), // X轴旋转角度(弧度)
0 * (Math.PI / 180), // Y轴旋转180度
90 * (Math.PI / 180), // Z轴旋转45度
)
rightRoof.position.set(
-sizeData.baseWidth / 2 + 20 + positionData.x,
roofPositionY + positionData.y,
positionData.z - 16.5,
)
rightRoof.name = 'rightRoof'
group.add(rightRoof)
// 创建左侧屋顶(对称)
const leftRoof = new THREE.Mesh(geometry, material)
leftRoof.rotation.set(
100 * (Math.PI / 180), // X轴旋转角度(弧度)
0 * (Math.PI / 180), // Y轴旋转180度
90 * (Math.PI / 180), // Z轴旋转45度
)
leftRoof.position.set(
sizeData.baseWidth / 2 - 20 + positionData.x,
roofPositionY + positionData.y,
positionData.z + 16.5,
)
leftRoof.name = 'leftRoof'
group.add(leftRoof)
return {
roofs: [rightRoof, leftRoof],
width: roofWidth,
}
}
function createDoor(sizeData, positionData) {
if (!group) return
// 创建门组
doorGroup = new THREE.Group()
doorGroup.name = 'doorGroup'
// 加载门纹理
const texture = new THREE.TextureLoader().load(sizeData.doorImg)
// 创建门几何体 - 宽度使用sizeData.doorWidth,高度使用sizeData.doorHeight
const door = new THREE.BoxGeometry(
sizeData.doorWidth,
sizeData.doorHeight,
0.5, // 厚度
)
// 创建门材质
const material = new THREE.MeshPhongMaterial({
map: texture,
transparent: true,
opacity: 1,
})
// 创建门网格
doorMesh = new THREE.Mesh(door, material)
doorMesh.name = 'door'
// 关键修改:将门网格在组中偏移,使旋转轴位于左侧边缘
doorMesh.position.x = sizeData.doorWidth / 2 // 将门向右移动宽度的一半,使左侧边缘位于原点
// 将门添加到门组
doorGroup.add(doorMesh)
// 设置门组位置和旋转
doorGroup.position.set(
sizeData.baseWidth / 2 + positionData.x,
sizeData.doorHeight / 2 + positionData.y,
6 + positionData.z - sizeData.baseThickness,
)
doorGroup.rotation.y = Math.PI / 2
// 将门组添加到场景组
group.add(doorGroup)
}
function animate() {
requestAnimationFrame(animate)
if (!camera || !marker) return // 添加null检查
// 让物体始终在 camera 前方 5 单位
marker.position.copy(camera.position)
marker.position.add(new THREE.Vector3(0, 0, -15).applyQuaternion(camera.quaternion))
if (renderer && scene && camera) {
renderer.render(scene, camera)
}
}
function createSkyBox() {
const texture = new THREE.TextureLoader().load(skyUrl)
texture.wrapS = texture.wrapT = THREE.RepeatWrapping
// texture.repeat.set(1, 1);
const skyBox = new THREE.SphereGeometry(500, 100, 100)
const material = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.BackSide,
})
const skyBoxMesh = new THREE.Mesh(skyBox, material)
group.add(skyBoxMesh)
}
// 新增:设置事件监听器函数
function setupEventListeners() {
if (!canvas) {
console.error('Canvas not initialized!') // 添加错误日志
return
}
console.log('22222222222') // 调试日志
// 移除旧监听器避免重复添加
canvas.removeEventListener('pointermove', onPointerMove)
canvas.removeEventListener('click', onClick)
window.removeEventListener('keydown', handleKeyDown) // 键盘事件需绑定到window
// 鼠标移动事件
canvas.addEventListener('pointermove', onPointerMove)
// 点击事件
canvas.addEventListener('click', onClick, false)
// 监听全局键盘事件
window.addEventListener('keydown', handleKeyDown)
}
// 新增:鼠标移动事件处理
function onPointerMove(event: MouseEvent) {
if (!canvas) return
// 将鼠标位置归一化为设备坐标 (-1 到 +1)
pointer.x = (event.clientX / canvas.clientWidth) * 2 - 1
pointer.y = -(event.clientY / canvas.clientHeight) * 2 + 1
}
// 新增:点击事件处理
function onClick() {
if (!scene || !camera) return
// 更新射线投射器
raycaster.setFromCamera(pointer, camera)
// 计算与场景对象的交点
const intersects = raycaster.intersectObjects(scene.children, true)
if (intersects.length > 0) {
const clickedObject = intersects[0].object
console.log('点击了:', clickedObject.name)
// 检查是否点击了门
if (clickedObject.name === 'door') {
handleDoorClick(clickedObject) // 修改:传入点击对象
}
}
}
// 新增:键盘事件处理函数
function handleKeyDown(event: KeyboardEvent) {
console.log('Key pressed:', event.key)
// 示例:用WASD控制相机移动
const moveSpeed = 5
if (!camera) return
console.log('相机的坐标:', camera.position)
switch (event.key.toLowerCase()) {
case 'w':
camera.position.z -= moveSpeed
break
case 's':
camera.position.z += moveSpeed
break
case 'a':
camera.position.x -= moveSpeed
break
case 'd':
camera.position.x += moveSpeed
break
case ' ':
// 空格键重置相机位置
camera.position.set(-30, 30, 50)
break
}
// 触发重新渲染
if (renderer && scene && camera) {
renderer.render(scene, camera)
}
}
onMounted(() => {
initThree()
createGround()
animate()
setupEventListeners() // 新增:设置事件监听器
createSkyBox()
inithouse(houseSize, housePosition)
inithouse(houseSize, housePositiontwo)
})
// 组件卸载时移除事件监听器
onUnmounted(() => {
if (canvas) {
canvas.removeEventListener('pointermove', onPointerMove)
canvas.removeEventListener('click', onClick)
window.removeEventListener('keydown', handleKeyDown) // 键盘事件需绑定到window
}
})
</script>
<style scoped>
.current-page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.draw {
width: 100%;
height: 100%;
}
</style>
操作事件代码:interactiveControls.ts
// interactiveControls.ts
import { ref } from 'vue'
import * as THREE from 'three'
// 中间件传递typeZi
// const typeZi = ref<string>()
// Three.js相关对象
let scene: THREE.Scene | null = null
let camera: THREE.PerspectiveCamera | null = null
let renderer: THREE.WebGLRenderer | null = null
let canvas: HTMLCanvasElement | null = null
const group = new THREE.Group()
let doorMesh: THREE.Mesh | null = null
const doorGroup = new THREE.Group()
const marker = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
new THREE.MeshBasicMaterial({ color: 0xff0000 }),
)
// 更新typeZi的方法
// const getTypeZi = async (value: string) => {
// typeZi.value = value
// }
// 新增:门点击处理函数
function handleDoorClick(doorObject: THREE.Object3D) {
console.log('门被点击了!')
// 获取门的父对象(如果是门组)
const doorParent = doorObject.parent || doorObject
const rotationSpeed = 0.05 // 旋转速度
const maxRotation = Math.PI / 2 // 最大旋转角度(90度)
// 检查当前门的状态(使用绝对值比较,避免方向问题)
const isOpen = Math.abs(doorParent.rotation.y) >= maxRotation
// 清除之前的动画(防止多次点击造成动画叠加)
if (doorParent.animationInterval) {
clearInterval(doorParent.animationInterval)
}
if (isOpen) {
// 关门动画
console.log('关门中...')
doorParent.animationInterval = setInterval(() => {
// 根据当前方向决定旋转方向
if (doorParent.rotation.y > 0) {
doorParent.rotation.y = Math.max(0, doorParent.rotation.y - rotationSpeed)
} else {
doorParent.rotation.y = Math.min(0, doorParent.rotation.y + rotationSpeed)
}
// 检查是否到达关闭位置
if (Math.abs(doorParent.rotation.y) < 0.01) {
doorParent.rotation.y = 0
clearInterval(doorParent.animationInterval)
}
}, 1000 / 60)
} else {
// 开门动画
console.log('开门中...')
doorParent.animationInterval = setInterval(() => {
// 根据当前方向决定旋转方向
if (doorParent.rotation.y >= 0) {
doorParent.rotation.y += rotationSpeed
// 检查是否到达最大开度
if (doorParent.rotation.y >= maxRotation) {
doorParent.rotation.y = maxRotation
clearInterval(doorParent.animationInterval)
}
} else {
doorParent.rotation.y -= rotationSpeed
// 检查是否到达最大开度
if (doorParent.rotation.y <= -maxRotation) {
doorParent.rotation.y = -maxRotation
clearInterval(doorParent.animationInterval)
}
}
}, 1000 / 60)
}
}
// 或者如果需要更新对象,可以导出函数形式
export function getThreeControls() {
return {
scene,
camera,
renderer,
canvas,
group,
doorMesh,
doorGroup,
marker,
handleDoorClick
}
}
配置数据代码:intehours.ts
// interactiveControls.ts
import { ref } from 'vue'
import * as THREE from 'three'
import wallbUrl from '@/assets/img/bmqiang.jpg'
import tileUrl from '@/assets/img/tile.jpg'
import doorUrl from '@/assets/img/door.jpg' // 新增门纹理导入
// 房子的尺寸参数
const houseSize = {
baseWidth: 40, // 房屋宽度
baseLength: 60, // 房屋长度
baseHeight: 20, // 墙面高度
baseThickness: 1, // 墙面厚度
peakHeight: 6, // 顶面高度(顶点突出高度)
peakWidth: 2, // 顶面宽度(顶点宽度)
doorWidth: 10, // 门宽度
doorHeight: 16, // 门高度
baseImg: wallbUrl, // 墙面图片
tileUrl: tileUrl, // 瓷砖图片
doorImg: doorUrl, // 新增门图片
}
// 房子的坐标
const housePosition = {
x: 0,
y: 1,
z: 0,
}
// 房子的坐标2
const housePositiontwo = {
x: 100,
y: 1,
z: 100,
}
// 或者如果需要更新对象,可以导出函数形式
export function getHoursData() {
return {
houseSize,
housePosition,
housePositiontwo
}
}
图片自己找一下