You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

313 lines
8.6 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div
id="plane"
:style="{
left: infoRef.style.left + 'px',
top: infoRef.style.top + 'px',
display: infoRef.style.display
}"
>
<p>机柜名称{{ infoRef.curCabinet.name }}</p>
<p>机柜温度{{ infoRef.curCabinet.temperature }}°</p>
<p>使用情况{{ infoRef.curCabinet.count }}/{{ infoRef.curCabinet.capacity }}</p>
</div>
<div ref="containerRef" class="container"></div>
</template>
<script setup lang="ts">
import {
WebGLRenderer,
PerspectiveCamera,
Scene,
Color,
Texture,
Mesh,
TextureLoader,
MeshBasicMaterial,
Raycaster,
Vector2,
MeshStandardMaterial,
Object3D,
RepeatWrapping
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { ref, reactive, onMounted, onUnmounted, watchEffect } from 'vue';
import TWEEN from '@tweenjs/tween.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const containerRef = ref<HTMLDivElement>();
const rendererRef = ref<WebGLRenderer>();
const cameraRef = ref<PerspectiveCamera>();
const controlRef = ref<OrbitControls>();
const maps: Map<string, Texture> = new Map();
const cabinets: Mesh[] = [];
const scene = new Scene();
const raycaster = new Raycaster();
const pointer = new Vector2();
const curCabinetRef = ref<Mesh | null>();
const infoRef = reactive({
style: { left: 0, top: 0, display: 'none' },
curCabinet: {
name: 'Loading……',
temperature: 0,
capacity: 0,
count: 0
}
});
// 初始化相机
function initCamera() {
const container = containerRef.value!;
cameraRef.value = new PerspectiveCamera(
45,
container.clientWidth / container.clientHeight, // 使用容器宽高比
0.1,
1000
);
const start = { x: 0, y: 0, z: 0 };
new TWEEN.Tween(start)
.to({ x: 0, y: 10, z: 15 }, 800)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(() => {
const { x, y, z } = start;
cameraRef.value?.position.set(x, y, z);
})
.start();
controlRef.value = new OrbitControls(cameraRef.value, rendererRef.value?.domElement);
controlRef.value.autoRotate = false;
controlRef.value.autoRotateSpeed = 1;
controlRef.value.dampingFactor = 0.2;
controlRef.value.rotateSpeed = 0.3; // 降低旋转速度(默认为 1.0
controlRef.value.enableDamping = true;
controlRef.value.enableRotate = true;
controlRef.value.minDistance = 10;
controlRef.value.maxPolarAngle = Math.PI * 0.5;
}
// 初始化渲染器
function initRenderer() {
const container = containerRef.value!;
rendererRef.value = new WebGLRenderer({
antialias: true, // 启用抗锯齿
alpha: true // 启用透明背景
});
rendererRef.value.setClearColor(new Color('#212d36'));
rendererRef.value.setSize(container.clientWidth, container.clientHeight); // 使用容器尺寸
rendererRef.value.shadowMap.enabled = true;
}
// 渲染循环
function render() {
requestAnimationFrame(render);
if (cameraRef.value) {
rendererRef.value?.render(scene, cameraRef.value);
}
controlRef.value?.update();
TWEEN.update();
}
// 纹理加载
function crtTexture(imgName: string) {
let curTexture = maps.get(imgName);
if (!curTexture) {
curTexture = new TextureLoader().load('/models/' + imgName);
curTexture.flipY = false;
curTexture.wrapS = RepeatWrapping;
curTexture.wrapT = RepeatWrapping;
maps.set(imgName, curTexture);
}
return curTexture;
}
// 材质切换
function changeMat(obj: Mesh, map: Texture | null, color: Color) {
if (map) {
obj.material = new MeshBasicMaterial({
map: crtTexture(map.name)
});
} else {
obj.material = new MeshBasicMaterial({ color });
}
}
// 鼠标交互逻辑
function onMouseOverCabinet({ name }: { name: string }) {
infoRef.style.display = 'block';
infoRef.curCabinet.name = name;
infoRef.curCabinet.temperature = Math.round(Math.random() * 100);
const capacity = Math.ceil(200 * Math.random());
infoRef.curCabinet.capacity = capacity;
infoRef.curCabinet.count = capacity - 5;
}
function onMouseOutCabinet() {
infoRef.style.display = 'none';
}
//原逻辑
function onMouseMoveCabinet(left: number, top: number) {
infoRef.style.left = left;
infoRef.style.top = top;
}
//原逻辑
// function selectCabinet(x: number, y: number) {
// const container = rendererRef.value!.domElement;
// const { width, height } = container;
// pointer.set((x / width) * 2 - 1, -(y / height) * 2 + 1);
// if (cameraRef.value) {
// raycaster.setFromCamera(pointer, cameraRef.value);
// }
// const intersect = raycaster.intersectObjects(cabinets)[0];
// let intersectObj = intersect ? (intersect.object as Mesh) : null;
// if (curCabinetRef.value && curCabinetRef.value !== intersectObj) {
// const material = curCabinetRef.value.material as MeshBasicMaterial;
// material.setValues({
// map: maps.get('cabinet.jpg')
// });
// }
// if (intersectObj) {
// onMouseMoveCabinet(x, y);
// if (intersectObj !== curCabinetRef.value) {
// curCabinetRef.value = intersectObj;
// const material = intersectObj.material as MeshBasicMaterial;
// material.setValues({
// map: maps.get('cabinet-hover.jpg')
// });
// onMouseOverCabinet(intersectObj);
// }
// } else if (curCabinetRef.value) {
// curCabinetRef.value = null;
// onMouseOutCabinet();
// }
// }
function selectCabinet(x: number, y: number) {
const container = rendererRef.value!.domElement;
const { width, height } = container;
// 鼠标坐标转裁剪坐标注意Y 轴翻转)
pointer.set((x / width) * 2 - 1, -(y / height) * 2 + 1);
if (cameraRef.value) {
raycaster.setFromCamera(pointer, cameraRef.value);
}
const intersect = raycaster.intersectObjects(cabinets)[0];
let intersectObj = intersect ? (intersect.object as Mesh) : null;
if (curCabinetRef.value && curCabinetRef.value !== intersectObj) {
const material = curCabinetRef.value.material as MeshBasicMaterial;
material.setValues({
map: maps.get('cabinet.jpg')
});
}
if (intersectObj) {
onMouseMoveCabinet(x, y);
if (intersectObj !== curCabinetRef.value) {
curCabinetRef.value = intersectObj;
const material = intersectObj.material as MeshBasicMaterial;
material.setValues({
map: maps.get('cabinet-hover.jpg')
});
onMouseOverCabinet(intersectObj);
}
} else if (curCabinetRef.value) {
curCabinetRef.value = null;
onMouseOutCabinet();
}
}
//原逻辑
// function mouseMove({ clientX, clientY }: { clientX: number; clientY: number }) {
// console.log('mouseMove', clientX, clientY);
// selectCabinet(clientX, clientY);
// }
function mouseMove(event: MouseEvent) {
const container = containerRef.value!;
const rect = container.getBoundingClientRect(); // 获取容器相对于视口的位置
const x = event.clientX - rect.left; // 相对于容器的 X 坐标
const y = event.clientY - rect.top; // 相对于容器的 Y 坐标
selectCabinet(x, y);
}
// 尺寸变化监听
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
crtTexture('cabinet-hover.jpg');
containerRef.value!.onmousemove = mouseMove;
const loader = new GLTFLoader();
loader.load('/models/machineRoom.gltf', (gltf: any) => {
gltf.scene.children.forEach((obj: Object3D) => {
console.log('obj', obj);
if (obj instanceof Mesh) {
const { map, color } = obj.material as MeshStandardMaterial;
changeMat(obj, map, color);
if (obj.name.includes('cabinet')) {
cabinets.push(obj);
}
}
});
scene.add(...gltf.scene.children);
});
initRenderer();
initCamera();
render();
// 监听容器尺寸变化
resizeObserver = new ResizeObserver(() => {
const container = containerRef.value!;
cameraRef.value!.aspect = container.clientWidth / container.clientHeight;
cameraRef.value!.updateProjectionMatrix();
rendererRef.value!.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(containerRef.value!);
});
onUnmounted(() => {
// 清理 ResizeObserver
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
watchEffect(() => {
if (rendererRef.value) {
containerRef.value?.append(rendererRef.value.domElement);
}
});
</script>
<style scoped>
.container {
position: relative; /* 确保容器定位正确 */
width: 100%;
height: 100%;
overflow: hidden; /* 防止滚动条干扰 */
}
#plane {
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
color: #b1acac;
padding: 0 18px;
transform: translate(12px, -100%);
display: block;
z-index: 9999; /* 提高层级,确保在最上层 */
}
</style>