1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
| <template> <canvas ref="canvasRef"></canvas> <div id="plane" :style="{display:planeDisplay,left:planePos.left,top:planePos.top}"> <p>机柜名称:{{ curShowCabinet.name }}</p> <p>机柜温度:{{ curShowCabinet.temperature }}°</p> <p>使用情况:{{ curShowCabinet.count }}/{{ curShowCabinet.capacity }}</p> </div> </template> <script setup lang="ts"> import * as THREE from "three"; import { onMounted, ref, reactive } from "vue"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import {getCabinetByName} from "@/api/request"
const canvasRef = ref(null);
// 纹理集合 let maps = new Map(); // 机柜集合 let cabinets = []; // 当前机柜 let curCabinet = null; // 展示机柜信息 const curShowCabinet = reactive({ // 名称 name: "Loading……", // 温度 temperature: 0, // 容量 capacity: 0, // 服务器数量 count: 0, });
//信息面板的位置 const planePos = reactive({ left: 0, top: 0, }); // 信息面板的可见性 const planeDisplay = ref("none");
onMounted(() => { // 1.创建场景 const scene = new THREE.Scene({ antialias: true, });
// 2.创建相机 const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
// 设置相机位置 camera.position.set(0, 10, 15); // 相机看向 camera.lookAt(0, 0, 0);
// 添加相机 scene.add(camera);
// 3.初始化渲染器 const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, });
//设置渲染的尺寸大小 renderer.setSize(window.innerWidth, window.innerHeight);
// 4.将webgl渲染的canvas内容添加到app上 document.querySelector('#app').appendChild(renderer.domElement);
// 5.创建轨道控制器 const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true;
// 6.持续渲染,让相机场景动起来 function render() { controls.update(); // 使用渲染器通过相机将场景渲染进来 renderer.render(scene, camera); // 渲染下一帧的时候就会调用render函数 requestAnimationFrame(render); }
render();
// 7.监听画面的变化,更新渲染的画面 window.addEventListener("resize", () => { // 更新摄像头 camera.aspect = window.innerWidth / window.innerHeight; // 更新摄像机的投影矩阵 camera.updateProjectionMatrix(); // 更新渲染器 renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比 renderer.setPixelRatio(window.devicePixelRatio); });
// 修改材质和图像源 function crtTexture(imgName) { let curTexture = maps.get(imgName); if (!curTexture) { curTexture = new THREE.TextureLoader().load("models/" + imgName); curTexture.flipY = false; curTexture.wrapS = 1000; curTexture.wrapT = 1000; maps.set(imgName, curTexture); } return curTexture; }
function changeMat(obj, map, color) { if (map) { obj.material = new THREE.MeshBasicMaterial({ map: crtTexture(map.name), }); } else { obj.material = new THREE.MeshBasicMaterial({ color }); } }
// 8.加载模型 const loader = new GLTFLoader(); loader.load("models/machineRoom.gltf", ({ scene: { children } }) => { children.forEach((obj) => { const { map, color } = obj.material; changeMat(obj, map, color); if (obj.name.includes("cabinet")) { cabinets.push(obj); } }); crtTexture("cabinet-hover.jpg"); scene.add(...children); canvasRef.value.addEventListener("mousemove", (e) => { selectCabinet(e.clientX, e.clientY); }); }); //鼠标划入机柜事件,参数为机柜对象 function onMouseMoveCabinet(left, top) { planePos.left = left + "px"; planePos.top = top + "px"; planeDisplay.value = "block"; } //鼠标划入机柜事件,参数为机柜对象 function onMouseOverCabinet(cabinet) { getCabinetByName(cabinet.name).then((res) => { curShowCabinet.name = res.name; curShowCabinet.temperature = res.temperature; curShowCabinet.capacity = res.capacity; curShowCabinet.count = res.count; }); } //鼠标划出机柜的事件 function onMouseOutCabinet() { planeDisplay.value = "none"; } //射线投射器,可基于鼠标点和相机,在世界坐标系内建立一条射线,用于选中模型 const raycaster = new THREE.Raycaster(); //鼠标在裁剪空间中的点位 const pointer = new THREE.Vector2(); // 选择机柜 function selectCabinet(x: number, y: number) { const { width, height } = renderer.domElement; // 鼠标的canvas坐标转裁剪坐标 pointer.set((x / width) * 2 - 1, -(y / height) * 2 + 1); // 基于鼠标点的裁剪坐标位和相机设置射线投射器 raycaster.setFromCamera(pointer, camera); // 选择机柜 const intersect = raycaster.intersectObjects(cabinets)[0]; let intersectObj = intersect ? intersect.object : null; // 若之前已有机柜被选择,且不等于当前所选择的机柜,取消之前选择的机柜的高亮 if (curCabinet && curCabinet !== intersectObj) { const material = curCabinet.material; material.setValues({ map: maps.get("cabinet.jpg"), }); } /* 若当前所选对象不为空: 触发鼠标在机柜上移动的事件。 若当前所选对象不等于上一次所选对象: 更新curCabinet。 将模型高亮。 触发鼠标划入机柜事件。 否则若上一次所选对象存在: 置空curCabinet。 触发鼠标划出机柜事件。 */ if (intersectObj) { onMouseMoveCabinet(x, y); if (intersectObj !== curCabinet) { curCabinet = intersectObj; const material = intersectObj.material; material.setValues({ map: maps.get("cabinet-hover.jpg"), }); onMouseOverCabinet(intersectObj); } } else if (curCabinet) { curCabinet = null; onMouseOutCabinet(); } } }); </script> <style> #plane { position: absolute; top: 200px; left: 0; background-color: rgba(0, 0, 0, 0.5); color: #fff; padding: 0 18px; transform: translate(12px, -100%); display: none; z-index: 999; } </style>
|