officialAccount/pages/map/index.vue

291 lines
7.4 KiB
Vue
Raw Normal View History

2025-05-26 08:59:24 +08:00
<template>
<div class="container">
<div ref="threeContainer" class="three-container"></div>
</div>
</template>
<script setup>
import {
onMounted,
ref
} from 'vue'
import * as THREE from 'three'
import {
OrbitControls
} from 'three/examples/jsm/controls/OrbitControls.js'
const threeContainer = ref(null)
onMounted(() => {
const container = threeContainer.value
const W = container.clientWidth,
H = 400
// Scene, Camera, Renderer
const scene = new THREE.Scene()
scene.background = new THREE.Color(0xf5f5f5)
const camera = new THREE.PerspectiveCamera(60, W / H, 0.1, 1000)
camera.position.set(0, 20, 30)
const renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setSize(W, H)
renderer.shadowMap.enabled = true
container.appendChild(renderer.domElement)
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5))
const dirL = new THREE.DirectionalLight(0xffffff, 0.8)
dirL.position.set(-10, 20, 10)
dirL.castShadow = true
scene.add(dirL)
// Floor
const ground = new THREE.Mesh(
new THREE.BoxGeometry(50, 0.02, 50),
new THREE.MeshStandardMaterial({
color: 0xdddddd
})
)
ground.position.y = -0.01
ground.receiveShadow = true
scene.add(ground)
// Materials
const wallMat = new THREE.MeshStandardMaterial({
color: 0xf8f8f8,
roughness: 0.7,
metalness: 0.1,
side: THREE.DoubleSide
})
const lintelMat = new THREE.MeshStandardMaterial({
color: 0xe0e0e0,
roughness: 0.6,
metalness: 0.1
})
const doorMat = new THREE.MeshStandardMaterial({
color: 0x8b4513,
roughness: 0.6,
metalness: 0.2
})
const bedFrameMat = new THREE.MeshStandardMaterial({
color: 0x555555,
roughness: 0.5,
metalness: 0.8
})
const mattressMat = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.8,
metalness: 0.1
})
const tvMat = new THREE.MeshStandardMaterial({
color: 0x000000,
roughness: 0.4,
metalness: 0.3
})
let selectedRoom = null
function markOriginal(mesh) {
mesh.userData.originalMaterial = mesh.material.clone()
}
function createRoom({
w,
h,
d,
x,
z,
door
}) {
const room = new THREE.Group()
room.position.set(x, h / 2, z)
room.userData.isRoom = true
room.userData.selected = false
const planeH = new THREE.PlaneGeometry(w, h)
const planeD = new THREE.PlaneGeometry(d, h)
// South with door
const upH = h - door.height - door.sill
const upWall = new THREE.Mesh(new THREE.PlaneGeometry(w, upH), wallMat.clone())
upWall.position.set(0, door.height + door.sill + upH / 2 - h / 2, d / 2)
markOriginal(upWall);
room.add(upWall)
const sideW = (w - door.width) / 2
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(sideW, door.height), wallMat.clone())
leftWall.position.set(-w / 2 + sideW / 2, door.sill + door.height / 2 - h / 2, d / 2)
markOriginal(leftWall);
room.add(leftWall)
const rightWall = leftWall.clone()
rightWall.position.x = w / 2 - sideW / 2
markOriginal(rightWall);
room.add(rightWall)
const lintel = new THREE.Mesh(new THREE.BoxGeometry(door.width, door.lintelThk, 0.1), lintelMat
.clone())
lintel.position.set(0, door.sill + door.height + door.lintelThk / 2 - h / 2, d / 2 + 0.05)
markOriginal(lintel);
room.add(lintel)
const doorGroup = new THREE.Group()
const doorMesh = new THREE.Mesh(new THREE.BoxGeometry(door.width, door.height, 0.05), doorMat.clone())
markOriginal(doorMesh);
doorMesh.position.set(door.width / 2, door.height / 2 - h / 2, 0)
doorGroup.add(doorMesh)
doorGroup.position.set(-w / 2 + sideW + 0.01, 0, d / 2 + 0.025)
doorGroup.userData.isDoor = true
doorGroup.userData.open = false
room.add(doorGroup)
// North, East, West walls
const north = new THREE.Mesh(planeH, wallMat.clone())
north.position.set(0, 0, -d / 2)
north.rotation.y = Math.PI
markOriginal(north);
room.add(north)
const east = new THREE.Mesh(planeD, wallMat.clone())
east.position.set(w / 2, 0, 0)
east.rotation.y = -Math.PI / 2
markOriginal(east);
room.add(east)
const west = east.clone()
west.position.x = -w / 2
west.rotation.y = Math.PI / 2
markOriginal(west);
room.add(west)
// Single Bed flush
const bedW = w * 0.6,
bedH = 0.5,
bedD = d * 0.4
const bed = new THREE.Group()
bed.userData.isBed = true
const frame = new THREE.Mesh(new THREE.BoxGeometry(bedW, 0.1, bedD), bedFrameMat.clone())
frame.position.y = -h / 2 + 0.05
markOriginal(frame);
bed.add(frame)
const mat = new THREE.Mesh(new THREE.BoxGeometry(bedW * 0.95, bedH, bedD * 0.95), mattressMat.clone())
mat.position.y = -h / 2 + bedH / 2 + 0.05
markOriginal(mat);
bed.add(mat)
bed.position.set(0, 0, d / 2 - bedD / 2 - 0.05)
room.add(bed)
// TV flush opposite
const tvWidth = bedW * 0.6
const tvHeight = tvWidth * 9 / 16
const tv = new THREE.Mesh(new THREE.BoxGeometry(tvWidth, tvHeight, 0.05), tvMat.clone())
tv.userData.isTV = true
markOriginal(tv);
tv.position.set(0, bedH + tvHeight / 2, -d / 2 + 0.05)
room.add(tv)
room.traverse(o => {
if (o.isMesh) {
o.castShadow = true
o.receiveShadow = true
}
})
return room
}
// Build grid
const building = new THREE.Group()
const cols = 5,
rows = 4,
sx = 6,
sz = 8
for (let i = 0; i < 20; i++) {
const col = i % cols,
row = Math.floor(i / cols)
const x = -((cols - 1) * sx) / 2 + col * sx
const z = ((rows - 1) * sz) / 2 - row * sz
building.add(createRoom({
w: 4,
h: 3,
d: 5,
x,
z,
door: {
width: 1,
height: 2,
sill: 0.1,
lintelThk: 0.2
}
}))
}
scene.add(building)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
const ray = new THREE.Raycaster(),
mouse = new THREE.Vector2()
renderer.domElement.addEventListener('click', e => {
const rect = renderer.domElement.getBoundingClientRect()
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1
ray.setFromCamera(mouse, camera)
const hits = ray.intersectObjects(building.children, true)
if (!hits.length) return
// Door
let obj = hits[0].object
const doorGrp = obj.userData.isDoor ? obj : obj.parent.userData.isDoor ? obj.parent : null
if (doorGrp) {
const open = !doorGrp.userData.open
doorGrp.rotation.y = open ? -Math.PI / 2 : 0
doorGrp.userData.open = open
return
}
// Room selection
let tgt = hits[0].object
while (tgt && !tgt.userData.isRoom) tgt = tgt.parent
if (!tgt) return
// Deselect previous
if (selectedRoom && selectedRoom !== tgt) {
selectedRoom.userData.selected = false
selectedRoom.traverse(n => {
if (n.isMesh && n.userData.originalMaterial) {
n.material = n.userData.originalMaterial.clone()
}
})
}
// Toggle
const sel = !tgt.userData.selected
tgt.userData.selected = sel
tgt.traverse(n => {
if (n.isMesh && n.userData.originalMaterial) {
let matClone = n.userData.originalMaterial.clone()
if (sel && !n.userData.isTV && !n.userData.isDoor) matClone.color.set(0x3377ff)
n.material = matClone
}
})
selectedRoom = sel ? tgt : null
})
const animate = () => {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera)
}
animate()
})
</script>
<style scoped>
.container {
width: 100%;
height: 400px
}
.three-container {
width: 100%;
height: 100%
}
</style>