首页 > 其他分享 >vue2项目中使用three.js开发三维IT机房

vue2项目中使用three.js开发三维IT机房

时间:2024-09-07 19:52:59浏览次数:7  
标签:const 鼠标 three js 机柜 THREE vue2 new curCabinet

三维IT机房可以将机房数据可视化,让企业更好的监控和管理IT 机柜

  • 在前端页面对IT 机房进行三维展示

  • 当鼠标划入IT 机柜的时候,提示当前机柜的详细信息

  • 一键显示机房中过热的机柜

1. 准备一份IT机房模型

图片

1-1-建模思路

  • 简化模型,能用贴图表现的细节,就用贴图。这样可提高渲染速度。

  • 将光效融入贴图中,即模型贴图后便具备光效和体感。这样在three 中就无需打灯,即可提高开发速度,亦可提高渲染效率。

1-2-建模软件

可以3d建模的软件有很多,3dsMax、ZRender、C4D 都可以。如使用3dsMax 建模和导出gltf。

1-3-模型文件

GLTF 模型文件包含了整个场景的数据,比如几何体、材质、动画、相机等。

GLTF 模型在使用起来,要比传统的obj 模型方便很多。

在导出GLTF模型后,一般会包含以下文件:

  • gltf 模型文件

  • bin文件

  • 贴图文件

图片

1-4-规范模型的结构和命名

  • 在建模软件中,同一类型的模型文件可以放入一个数组里,数组可以多层嵌套。

  • 当前的机房模型比较简单,没有使用数组,所有的Mesh对象都是平展开的。

为了便于访问和区分模型,需要对模型进行规范命名,如机房中的IT机柜都是按照cabinet-001、cabinet-002、cabinet-003 命名的。

假设IT机柜的名称都是唯一的,那我们便可以基于这个名称从后端获取相应机柜的详细信息。

图片

2、Vue项目 加载GLTF格式模型

2.1 安装three 相关的依赖

bash

npm install three @types/three --save --registry=https://registry.npm.taobao.org

图片

2.2 Vue项目 构建本地3D模型

图片


xml

<template>
<div id="container"></div>
</template>

<script>
import * as Three from 'three'
let scene = null,
camera=null,
renderer=null,
mesh=null
export default {
data () {
return {

};
},
methods:{
init(){
let container = document.getElementById('container');
camera = new Three.PerspectiveCamera(70, container.clientWidth/container.clientHeight, 0.01, 10);
camera.position.z = 1
scene = new Three.Scene()
let geometry = new Three.BoxGeometry(0.2, 0.2, 0.2);
let material = new Three.MeshNormalMaterial();

mesh = new Three.Mesh(geometry, material);
scene.add(mesh);

renderer = new Three.WebGLRenderer({antialias:true});
renderer.setSize(container.clientWidth,container.clientHeight);
container.appendChild(renderer.domElement);
},
animate(){
requestAnimationFrame(this.animate);
console.log(this.animate,'132')
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene,camera);
}
},
mounted(){
this.init()
this.animate()
}

}

</script>
<style scoped>
#container{
width: 100vw;
height: 100vh;
}
</style>

 

2.3 Vue项目导入外部GLTF格式模型——GLTFLoader()

2.3.1 在public文件夹放置外部模型

GLTF格式模型文件下载:pan.baidu.com/s/16KAD-E-4…

在public文件夹里建立一个models 文件夹,将之前的模型文件放进去

图片

2.3.2 GLTF 模型的导入、渲染和相机变换

xml

<template> <div>

<div id="container"></div>
</div>
</template>

<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

export default {
data() {
return {
mesh: null,
camera: null,
scene: null,
renderer: null,
controls: null
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.createScene() // 创建场景
this.loadGLTF() // 加载GLTF模型
// this.createLight() // 创建光源
this.createCamera() // 创建相机
this.createRender() // 创建渲染器
this.createControls() // 创建控件对象
this.render() // 渲染
},
// 创建场景
createScene() {
this.scene = new THREE.Scene()
},
// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);
this.scene.add(...children)
})
},

// 创建光源
createLight() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) // 创建环境光
this.scene.add(ambientLight) // 将环境光添加到场景

const spotLight = new THREE.SpotLight(0xffffff) // 创建聚光灯
spotLight.position.set(150, 150, 150)
spotLight.castShadow = true
this.scene.add(spotLight)
},
// 创建相机
createCamera() {
const element = document.getElementById('container')
const width = element.clientWidth // 窗口宽度
const height = element.clientHeight // 窗口高度
const k = width / height // 窗口宽高比
// PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
this.camera.position.set(0, 10, 15) // 设置相机位置

this.camera.lookAt(0, 0, 0) // 设置相机方向
this.scene.add(this.camera)
},
// 创建渲染器
createRender() {
const element = document.getElementById('container')
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
// this.renderer.shadowMap.enabled = true // 显示阴影
// this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
element.appendChild(this.renderer.domElement)
},

render() {
// if (this.mesh) {
// this.mesh.rotation.z += 0.006
// }
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
// 创建控件对象
createControls() {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
}
}
}
</script>
<style>
#container {
position: absolute;
width: 100vw;
height: 100vh;
}
</style>

 

图片

和之前的3dsmax 图片对比一下,就会发现:贴图颜色变深了

3. 调整外部3D模型数据

3.1 分析一下模型数据

javascript

// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);
this.scene.add(...children)
})
},

分析一下children里的Mesh 对象,可以发现:所有Mesh对象的material材质都是MeshStandardMaterial 类型。

图片

再分析一下material 中的map 贴图,可以发现其map贴图为 Texture 对象,其具备以下重要信息:

  • name 是贴图图片的名称。

  • flipY为false,即不对图像的y轴做翻转。

  • image图像源是ImageBitmap 类型。

  • wrapS 纹理横向重复,即THREE.RepeatWrapping。

  • wrapT 纹理纵向重复,即THREE.RepeatWrapping。

注:THREE.RepeatWrapping=1000

图片

在此,我们要知道以下threejs 知识:

  • MeshStandardMaterial 材质会感光,我们不需要打光,需要将其材质换成MeshBasicMaterial。

  • ImageBitmap 的图像类型是渲染效果变黑的关键原因,因此需要将其换成Image() 对象。

接下来给模型换一个材质和图像源。

3.2 修改材质和图像源

  1. 添加maps属性,用来存储纹理对象,以避免贴图的重复加载


javascript

// ES6的Map数据结构
this.maps = new Map()

  1. 在加载GLTF 的时候,用changeMat()方法修改Mesh 对象的材质

javascript

// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);

children.forEach((obj) => {
const { map, color} = obj.material
this.changeMat(obj, map, color)
})

this.scene.add(...children)
})
},

  1. 添加一个修改材质的方法changeMat()

arduino

changeMat(obj, map, color) {
if (map) {
obj.material = new THREE.MeshBasicMaterial({
map: this.crtTexture(map.name)
})
} else {
obj.material = new THREE.MeshBasicMaterial({color})
}
},

changeMat() 方法的参数:

  • obj:需要修改材质的Mesh 对象

  • map:GLTF 模型里的贴图对象

  • color:GLTF 模型的颜色

其中的if 逻辑是:若Mesh模型有贴图,就为其换一个材质和贴图;否则,就换一个材质,并继承原GLTF 模型的颜色。 

图片

  1. 添加建立纹理对象的方法crtTexture()

ini

crtTexture(imgName) {
let curTexture = this.maps.get(imgName)
if (!curTexture) {
curTexture=new THREE.TextureLoader().load('./models/'+imgName)
curTexture.flipY = false
curTexture.wrapS = 1000
curTexture.wrapT = 1000
this.maps.set(
imgName,
curTexture
)
}
return curTexture
},

crtTexture() 会根据贴图名称建立Texture 纹理对象。

  • TextureLoader().load() 可以根据贴图路径,加载贴图,返回一个Texture 对象。

  • curTexture 的flipY、wrapS、wrapT是对原始GLTF 贴图的相应属性的继承。

图片

这样就和3dsmax中的模型效果一致了。

图片

4. 模型选择——添加鼠标事件

对IT 机柜做选择,让选中的机柜高亮

  1. 添加两个属性、三个鼠标事件

csharp

//机柜集合
cabinets: [],
//鼠标划入的机柜
curCabinet: '',

scss

//鼠标划入机柜事件,参数为机柜对象
onm ouseOverCabinet (cabinet) {console.log(cabinet); },
//鼠标在机柜上移动的事件,参数为鼠标在canvas画布上的坐标位
onm ouseMoveCabinet(x,y) {console.log(x,y); },
//鼠标划出机柜的事件
onm ouseOutCabinet () { }

  1. 为maps 添加一个机柜的高亮贴图。之后鼠标划入机柜时,会将其贴图更换为高亮贴图。

javascript

mounted() {
this.maps = new Map()
this.crtTexture("cabinet-hover.jpg")
// this.init()
},

  1. 在加载GLTF 模型时,若模型名称包含'cabinet',便将其存入cabinets

javascript

// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);

children.forEach((obj) => {
const { map, color} = obj.material
this.changeMat(obj, map, color)
if (obj.name.includes('cabinet')) {
this.cabinets.push(obj)
}
})

this.scene.add(...children)
})
},

  1. 建立一个射线投射器,一个二维点,以避免在鼠标选择时机柜时重复实例化;

    添加选择模型的方法selectCabinet(x,y),其参数为鼠标的坐标位

kotlin

selectCabinet(x, y) {
const {cabinets,renderer,camera,maps,curCabinet}=this
const { width, height } = renderer.domElement
//射线投射器,可基于鼠标点和相机,在世界坐标系内建立一条射线,用于选中模型
const raycaster = new THREE.Raycaster()
//鼠标在裁剪空间中的点位
const pointer = new THREE.Vector2()

// 鼠标的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) {
this.onMouseMoveCabinet(x,y)
if (intersectObj !== curCabinet) {
this.curCabinet= intersectObj
const material = intersectObj.material
material.setValues({
map: maps.get('cabinet-hover.jpg')
})
this.onMouseOverCabinet(intersectObj)
}
} else if(curCabinet) {
this.curCabinet = null
this.onMouseOutCabinet()
}
},

  1. 为容器添加鼠标移动事件

xml

<template>
<div>
<div id="container" @mousemove="mouseMove"></div>
</div>
</template>

javascript

// 鼠标移动事件
mouseMove({clientX,clientY}) {
this.selectCabinet(clientX, clientY)
},

4.1 机柜选中效果

图片

kotlin

<template>
<div>
<div id="container" @mousemove="mouseMove"></div>
</div>
</template>

<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

export default {
data() {
return {
mesh: null,
camera: null,
scene: null,
renderer: null,
controls: null,
maps: null,
//机柜集合
cabinets: [],
//鼠标划入的机柜
curCabinet: '',
// raycaster: '',
// pointer: '',
}
},
mounted() {
this.maps = new Map()
this.crtTexture("cabinet-hover.jpg")
this.init()
},
methods: {
// 初始化
init() {
this.createScene() // 创建场景
this.loadGLTF() // 加载GLTF模型
// this.createLight() // 创建光源
this.createCamera() // 创建相机
this.createRender() // 创建渲染器
this.createControls() // 创建控件对象
this.render() // 渲染

},
// 创建场景
createScene() {
this.scene = new THREE.Scene()
},
// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);

children.forEach((obj) => {
const { map, color} = obj.material
this.changeMat(obj, map, color)
if (obj.name.includes('cabinet')) {
this.cabinets.push(obj)
}
})

this.scene.add(...children)
})
},

// 创建光源
createLight() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) // 创建环境光
this.scene.add(ambientLight) // 将环境光添加到场景

const spotLight = new THREE.SpotLight(0xffffff) // 创建聚光灯
spotLight.position.set(150, 150, 150)
spotLight.castShadow = true
this.scene.add(spotLight)
},
// 创建相机
createCamera() {
const element = document.getElementById('container')
const width = element.clientWidth // 窗口宽度
const height = element.clientHeight // 窗口高度
const k = width / height // 窗口宽高比
// PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
this.camera.position.set(0, 10, 15) // 设置相机位置

this.camera.lookAt(0, 0, 0) // 设置相机方向
this.scene.add(this.camera)
},
// 创建渲染器
createRender() {
const element = document.getElementById('container')
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
// this.renderer.shadowMap.enabled = true // 显示阴影
// this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
element.appendChild(this.renderer.domElement)
},

render() {
// if (this.mesh) {
// this.mesh.rotation.z += 0.006
// }
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
// 创建控件对象
createControls() {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
},
changeMat(obj, map, color) {
if (map) {
obj.material = new THREE.MeshBasicMaterial({
map: this.crtTexture(map.name)
})
} else {
obj.material = new THREE.MeshBasicMaterial({color})
}
},
crtTexture(imgName) {
let curTexture = this.maps.get(imgName)
if (!curTexture) {
curTexture=new THREE.TextureLoader().load('./models/'+imgName)
curTexture.flipY = false
curTexture.wrapS = 1000
curTexture.wrapT = 1000
this.maps.set(
imgName,
curTexture
)
}
return curTexture
},
selectCabinet(x, y) {
const {cabinets,renderer,camera,maps,curCabinet}=this
const { width, height } = renderer.domElement
//射线投射器,可基于鼠标点和相机,在世界坐标系内建立一条射线,用于选中模型
const raycaster = new THREE.Raycaster()
//鼠标在裁剪空间中的点位
const pointer = new THREE.Vector2()

// 鼠标的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) {
this.onMouseMoveCabinet(x,y)
if (intersectObj !== curCabinet) {
this.curCabinet= intersectObj
const material = intersectObj.material
material.setValues({
map: maps.get('cabinet-hover.jpg')
})
this.onMouseOverCabinet(intersectObj)
}
} else if(curCabinet) {
this.curCabinet = null
this.onMouseOutCabinet()
}
},
// 鼠标移动事件
mouseMove({clientX,clientY}) {
this.selectCabinet(clientX, clientY)
},
//鼠标划入机柜事件,参数为机柜对象
onm ouseOverCabinet (cabinet) {console.log(cabinet); },
//鼠标在机柜上移动的事件,参数为鼠标在canvas画布上的坐标位
onm ouseMoveCabinet(x,y) {console.log(x,y); },
//鼠标划出机柜的事件
onm ouseOutCabinet () { }
}
}
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}

</style>

5-信息提示

在鼠标划入IT机柜的时候,提示机柜的详细信息。

其最简单的做法就是用HTML 建立一个信息面板,当鼠标在IT机柜上移动的时候,就让其随鼠标移动。

5.1.建立信息提示板

less

state: {
planePos: {
//信息面板的位置
left: 0,
top:0
},
//信息面板的可见性
planeDisplay: 'none',
//机柜信息
curCabinet: {
//名称
name:'Loading……',
//温度
temperature: 0,
//容量
capacity: 0,
//服务器数量
count:0
}
}

xml

<template>
<div>
<div id="container" @mousemove="mouseMove">
<div
id='plane'
:style="{left: state.planePos.left,top:state.planePos.top,display: state.planeDisplay}"
>
<p>机柜名称:{{ state.curCabinet.name }}</p>
<p>机柜温度:{{ state.curCabinet.temperature }}°</p>
<p>使用情况:{{ state.curCabinet.count}} / {{ state.curCabinet.capacity}}</p>
</div>
</div>
</div>
</template>

设置面板样式

css

#plane{
position: absolute;
top: 0;
left: 0;
background-color: rgba(0,0,0,0.5);
color: #fff;
padding: 0 18px;
transform: translate(12px,-100%);
display: none;
}

5.2.设置信息面板的可见性和位置

根据绑定在机柜对象上的鼠标事件,设置信息面板的可见性和位置

javascript

//鼠标划入机柜事件,参数为机柜对象
onm ouseOverCabinet (cabinet) {
console.log(cabinet);
this.state.planeDisplay = 'block'
},
//鼠标在机柜上移动的事件,参数为鼠标在canvas画布上的坐标位
onm ouseMoveCabinet(x,y) {
console.log(x,y);
this.state.planePos.left = x + 'px'
this.state.planePos.top = y + 'px'
},
//鼠标划出机柜的事件
onm ouseOutCabinet () {
this.state.planeDisplay = 'none'
}

5.3 信息提示的页面效果


xml

<template>
<div>
<div id="container" @mousemove="mouseMove">
<div
id='plane'
:style="{left: state.planePos.left,top:state.planePos.top,display: state.planeDisplay}"
>
<p>机柜名称:{{ state.curCabinet.name }}</p>
<p>机柜温度:{{ state.curCabinet.temperature }}°</p>
<p>使用情况:{{ state.curCabinet.count}} / {{ state.curCabinet.capacity}}</p>
</div>
</div>
</div>
</template>

<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

export default {
data() {
return {
mesh: null,
camera: null,
scene: null,
renderer: null,
controls: null,
maps: null,
//机柜集合
cabinets: [],
//鼠标划入的机柜
curCabinet: '',
state: {
planePos: {
//信息面板的位置
left: 0,
top:0
},
//信息面板的可见性
planeDisplay: 'none',
//机柜信息
curCabinet: {
//名称
name:'Loading……',
//温度
temperature: 0,
//容量
capacity: 0,
//服务器数量
count:0
}
}
}
},
mounted() {
this.maps = new Map()
this.crtTexture("cabinet-hover.jpg")
this.init()
},
methods: {
// 初始化
init() {
this.createScene() // 创建场景
this.loadGLTF() // 加载GLTF模型
// this.createLight() // 创建光源
this.createCamera() // 创建相机
this.createRender() // 创建渲染器
this.createControls() // 创建控件对象
this.render() // 渲染

},
// 创建场景
createScene() {
this.scene = new THREE.Scene()
},
// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);

children.forEach((obj) => {
const { map, color} = obj.material
this.changeMat(obj, map, color)
if (obj.name.includes('cabinet')) {
this.cabinets.push(obj)
}
})

this.scene.add(...children)
})
},

// 创建光源
createLight() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) // 创建环境光
this.scene.add(ambientLight) // 将环境光添加到场景

const spotLight = new THREE.SpotLight(0xffffff) // 创建聚光灯
spotLight.position.set(150, 150, 150)
spotLight.castShadow = true
this.scene.add(spotLight)
},
// 创建相机
createCamera() {
const element = document.getElementById('container')
const width = element.clientWidth // 窗口宽度
const height = element.clientHeight // 窗口高度
const k = width / height // 窗口宽高比
// PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
this.camera.position.set(0, 10, 15) // 设置相机位置

this.camera.lookAt(0, 0, 0) // 设置相机方向
this.scene.add(this.camera)
},
// 创建渲染器
createRender() {
const element = document.getElementById('container')
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
// this.renderer.shadowMap.enabled = true // 显示阴影
// this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
element.appendChild(this.renderer.domElement)
},

render() {
// if (this.mesh) {
// this.mesh.rotation.z += 0.006
// }
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
// 创建控件对象
createControls() {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
},
changeMat(obj, map, color) {
if (map) {
obj.material = new THREE.MeshBasicMaterial({
map: this.crtTexture(map.name)
})
} else {
obj.material = new THREE.MeshBasicMaterial({color})
}
},
crtTexture(imgName) {
let curTexture = this.maps.get(imgName)
if (!curTexture) {
curTexture=new THREE.TextureLoader().load('./models/'+imgName)
curTexture.flipY = false
curTexture.wrapS = 1000
curTexture.wrapT = 1000
this.maps.set(
imgName,
curTexture
)
}
return curTexture
},
selectCabinet(x, y) {
const {cabinets,renderer,camera,maps,curCabinet}=this
const { width, height } = renderer.domElement
//射线投射器,可基于鼠标点和相机,在世界坐标系内建立一条射线,用于选中模型
const raycaster = new THREE.Raycaster()
//鼠标在裁剪空间中的点位
const pointer = new THREE.Vector2()

// 鼠标的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) {
this.onMouseMoveCabinet(x,y)
if (intersectObj !== curCabinet) {
this.curCabinet= intersectObj
const material = intersectObj.material
material.setValues({
map: maps.get('cabinet-hover.jpg')
})
this.onMouseOverCabinet(intersectObj)
}
} else if(curCabinet) {
this.curCabinet = null
this.onMouseOutCabinet()
}
},
// 鼠标移动事件
mouseMove({clientX,clientY}) {
this.selectCabinet(clientX, clientY)
},
//鼠标划入机柜事件,参数为机柜对象
onm ouseOverCabinet (cabinet) {
console.log(cabinet);
this.state.planeDisplay = 'block'
},
//鼠标在机柜上移动的事件,参数为鼠标在canvas画布上的坐标位
onm ouseMoveCabinet(x,y) {
console.log(x,y);
this.state.planePos.left = x + 'px'
this.state.planePos.top = y + 'px'
},
//鼠标划出机柜的事件
onm ouseOutCabinet () {
this.state.planeDisplay = 'none'
}
}
}
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
#plane{
position: absolute;
top: 0;
left: 0;
background-color: rgba(0,0,0,0.5);
color: #fff;
padding: 0 18px;
transform: translate(12px,-100%);
display: none;
}
</style>

 

6. 使用Apifox 添加获取机柜信息的接口

参照Apifox自带的示例项目,自己添加一个接口,根据IT机柜名获取机柜信息

7. 获取并展示机柜信息面板的内容

当鼠标划入IT机柜时,根据机柜名请求机柜数据,更新相应的state

javascript

getCabinateByName(name) {
let path = 'http://127.0.0.1:4523/m1/2003080-0-default/name/'
return fetch(path + name).then((res) => res.json());
}

kotlin

//鼠标划入机柜事件,参数为机柜对象
onm ouseOverCabinet (cabinet) {
console.log(cabinet.name);
this.state.planeDisplay = 'block'
//基于cabinet.name 获取机柜数据
this.getCabinateByName(cabinet.name).then(({ data }) => {
this.state.curCabinet = { ...data, name: cabinet.name }

});
},

7.1 最终效果


xml

<template>
<div>
<div id="container" @mousemove="mouseMove">
<div
id='plane'
:style="{left: state.planePos.left,top:state.planePos.top,display: state.planeDisplay}"
>
<p>机柜名称:{{ state.curCabinet.name }}</p>
<p>机柜温度:{{ state.curCabinet.temperature }}°</p>
<p>使用情况:{{ state.curCabinet.count}} / {{ state.curCabinet.capacity}}</p>
</div>
</div>
</div>
</template>

<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

export default {
data() {
return {
mesh: null,
camera: null,
scene: null,
renderer: null,
controls: null,
maps: null,
//机柜集合
cabinets: [],
//鼠标划入的机柜
curCabinet: '',
state: {
planePos: {
//信息面板的位置
left: 0,
top:0
},
//信息面板的可见性
planeDisplay: 'none',
//机柜信息
curCabinet: {
//名称
name:'Loading……',
//温度
temperature: 0,
//容量
capacity: 0,
//服务器数量
count:0
}
}
}
},
mounted() {
this.maps = new Map()
this.crtTexture("cabinet-hover.jpg")
this.init()
},
methods: {
// 初始化
init() {
this.createScene() // 创建场景
this.loadGLTF() // 加载GLTF模型
// this.createLight() // 创建光源
this.createCamera() // 创建相机
this.createRender() // 创建渲染器
this.createControls() // 创建控件对象
this.render() // 渲染

},
// 创建场景
createScene() {
this.scene = new THREE.Scene()
},
// 加载GLTF模型
loadGLTF() {
const loader = new GLTFLoader()
loader.load(`./models/machineRoom.gltf`, ({ scene: { children } }) => {
console.log(...children);

children.forEach((obj) => {
const { map, color} = obj.material
this.changeMat(obj, map, color)
if (obj.name.includes('cabinet')) {
this.cabinets.push(obj)
}
})

this.scene.add(...children)
})
},

// 创建光源
createLight() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) // 创建环境光
this.scene.add(ambientLight) // 将环境光添加到场景

const spotLight = new THREE.SpotLight(0xffffff) // 创建聚光灯
spotLight.position.set(150, 150, 150)
spotLight.castShadow = true
this.scene.add(spotLight)
},
// 创建相机
createCamera() {
const element = document.getElementById('container')
const width = element.clientWidth // 窗口宽度
const height = element.clientHeight // 窗口高度
const k = width / height // 窗口宽高比
// PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
this.camera.position.set(0, 10, 15) // 设置相机位置

this.camera.lookAt(0, 0, 0) // 设置相机方向
this.scene.add(this.camera)
},
// 创建渲染器
createRender() {
const element = document.getElementById('container')
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
// this.renderer.shadowMap.enabled = true // 显示阴影
// this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
element.appendChild(this.renderer.domElement)
},

render() {
// if (this.mesh) {
// this.mesh.rotation.z += 0.006
// }
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
// 创建控件对象
createControls() {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
},
changeMat(obj, map, color) {
if (map) {
obj.material = new THREE.MeshBasicMaterial({
map: this.crtTexture(map.name)
})
} else {
obj.material = new THREE.MeshBasicMaterial({color})
}
},
crtTexture(imgName) {
let curTexture = this.maps.get(imgName)
if (!curTexture) {
curTexture=new THREE.TextureLoader().load('./models/'+imgName)
curTexture.flipY = false
curTexture.wrapS = 1000
curTexture.wrapT = 1000
this.maps.set(
imgName,
curTexture
)
}
return curTexture
},
selectCabinet(x, y) {
const {cabinets,renderer,camera,maps,curCabinet}=this
const { width, height } = renderer.domElement
//射线投射器,可基于鼠标点和相机,在世界坐标系内建立一条射线,用于选中模型
const raycaster = new THREE.Raycaster()
//鼠标在裁剪空间中的点位
const pointer = new THREE.Vector2()

// 鼠标的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) {
this.onMouseMoveCabinet(x,y)
if (intersectObj !== curCabinet) {
this.curCabinet= intersectObj
const material = intersectObj.material
material.setValues({
map: maps.get('cabinet-hover.jpg')
})
this.onMouseOverCabinet(intersectObj)
}
} else if(curCabinet) {
this.curCabinet = null
this.onMouseOutCabinet()
}
},
// 鼠标移动事件
mouseMove({clientX,clientY}) {
this.selectCabinet(clientX, clientY)
},
//鼠标划入机柜事件,参数为机柜对象
onm ouseOverCabinet (cabinet) {
console.log(cabinet.name);
this.state.planeDisplay = 'block'
//基于cabinet.name 获取机柜数据
this.getCabinateByName(cabinet.name).then(({ data }) => {
this.state.curCabinet = { ...data, name: cabinet.name }

});
},
//鼠标在机柜上移动的事件,参数为鼠标在canvas画布上的坐标位
onm ouseMoveCabinet(x,y) {
// console.log(x,y);
this.state.planePos.left = x + 'px'
this.state.planePos.top = y + 'px'
},
//鼠标划出机柜的事件
onm ouseOutCabinet () {
this.state.planeDisplay = 'none'
},
getCabinateByName(name) {
let path = 'http://127.0.0.1:4523/m1/2003080-0-default/name/'
return fetch(path + name).then((res) => res.json());
}
}
}
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
#plane{
position: absolute;
top: 0;
left: 0;
background-color: rgba(0,0,0,0.5);
color: #fff;
padding: 0 18px;
transform: translate(12px,-100%);
display: none;
text-align: left;
}
</style>

标签:const,鼠标,three,js,机柜,THREE,vue2,new,curCabinet
From: https://blog.csdn.net/qq_35430208/article/details/142001240

相关文章

  • 用Threejs搭建一个Web3D汽车展厅!
    在网页里360度展示它家新款汽车的3d模型,还要可以让用户DIY汽车部件的颜色。先看最终效果3D引擎的基本知识本文的目标是让大家看完之后可以立刻上手用起来,既然要用3d引擎,那我们理解了一些3d的基本知识后,再看threejs的API文档效率就会很高。无论什么3d引擎,都不外乎由以下几种......
  • js逆向基础14异步编程3
    上节课遗留.finally.finally()方法不管Promise对象最后的状态如何都会执行.finally()方法的回调函数不接收任何的参数,也就是你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的它最终返回的默认会是一个上一次的Promise对象值,不过抛出的是一个异常......
  • 【开源免费】基于SpringBoot+Vue.JS网上订餐系统(JAVA毕业设计)
    本文项目编号T018,文末自助获取源码\color{red}{T018,文末自助获取源码}......
  • 基于sprigboot、vue.js、elementui、axios.js、xlsx.js的小型购物管理系统
    该管理系统实现了增加、编辑、删除、导出、批量删除。以下是代码实现:<!DOCTYPEhtml><html>   <head>      <metacharset="utf-8">      <title></title>      <linkrel="stylesheet"href="./css/element.css"/>......
  • 京东h5st参数js逆向
    扣代码的环节挺简单的就不讲了直接到重点发现许多包都会有一个h5st的加密参数那么我们就要看这个参数是怎么生成的我们可以根据请求堆栈找到h5st的入口当然还有一种更简单的方法就是直接全局搜索h5st这里采用后者这里sign函数就是h5st生成的地方但是这是个promi......
  • 安装nvm管理nodejs包,保姆级教程!
    安装nvm管理nodejs包,保姆级教程!一.本期所需网址nvm:github下载nvm:手册文档下载nvm命令查看:查看命令二.下载nvm1.github下载点击链接下载:https://github.com/coreybutler/nvm-windows/releases选择.exe文件下载安装开始安装双击打开安装程序同意协议下一步......
  • labelme标注的大图用sahi切成小图和小json
    1.去除labelme标注中只有1或2个点的轮廓指有些标注错误的只有1个点或2个点的轮廓不能转化为polygon点击查看代码importcv2importnumpyasnpimportjsonimportosdefremove_specific_labels(json_file):#读取JSON文件withopen(json_file,'rb+')asf:......
  • JS流程控制
    流程控制在任何一门程序设计语言中,都需要支持满足程序结构化所需要的三种流程控制:顺序控制分支控制(条件控制)循环控制顺序控制:在程序流程控制中,最基本的就是顺序控制。程序会按照自上而下的顺序执行。if语句【重要】分类:单分支,双分支,多分支,分支嵌套()只能是变量或值......
  • json字符串转义格式化后再转换处理demo StringEscapeUtils.unescapeJava
    json字符串转义格式化后再转换处理demoStringEscapeUtils.unescapeJava报错关键字:illegalidentifierExpectedBEGIN_OBJECTbutExpectednameatpackagecom.example.core.mydemo;importcom.alibaba.fastjson.JSON;importcom.fasterxml.jackson.core.JsonProcessingE......
  • 微服务实战——品牌管理(JSR303,SKU&SPU)
    品牌管理1.JSR3031、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示2、开启校验功能@Valid  效果:校验错误以后会有默认的响应;3、给校验的参数bean后紧跟一个BindingResult,就可以获取到校验的结果4、分组校验(多场景的复杂校验)    1......