3D-Demo/1_wind/index.html

576 lines
17 KiB
HTML
Raw Normal View History

2022-10-14 16:50:20 +08:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>风机Demo</title>
<style>
body {
margin:0;
overflow: hidden;
color: #FFF;
position: relative
/* 隐藏body窗口区域滚动条 */
}
.header-box{
width: 100%;
height: 126px;
position:absolute;
background: url(./assets/images/header-bg.png) no-repeat;
background-size: 100% 100%;
top: -30px;
z-index: 200;
}
.titile{
font-size: 36px;
margin-top: 40px;
font-weight: 500;
font-family: FZLanTingHeiS-B-GB;
text-align: center;
}
.echarts-box{
height: 28vh;
width: 35.08vw;
left: 20px;
bottom: 40px;
position:absolute
}
.equipmentLabel {
z-index: 999;
width: 988px;
height: 451px;
}
.equipmentLabel > li:nth-child(1) {
color: transparent;
width: 191px;
height: 225px;
background-image: url("./assets/images/1.png");
background-size: 191px auto;
position: absolute;
right: 302px;
top: 0;
}
.equipmentLabel .labelInfo {
color: transparent;
width: 302px;
height: 225px;
background-image: url("./assets/images/2.png");
background-size: 302px auto;
position: absolute;
right: 0;
top: 0;
padding: 10px;
box-sizing: border-box;
}
.equipmentLabel .labelInfo > div {
width: 100%;
height: 100%;
background-color: #04669e73;
border: 1px solid #15c5e8;
box-sizing: border-box;
padding: 20px 20px;
}
.equipmentLabel .labelInfo > div header {
width: 100%;
text-align: left;
font-size: 14px;
line-height: 20px;
color: #fff;
border-bottom: 1px dashed aqua;
padding-bottom: 14px;
}
.equipmentLabel .labelInfo > div header .en {
font-size: 12px;
color: aqua;
}
.equipmentLabel .labelInfo > div ul {
width: 100%;
color: #fff;
}
.equipmentLabel .labelInfo > div ul li {
line-height: 30px;
font-size: 14px;
display: flex;
text-align: left;
align-items: center;
}
.equipmentLabel .labelInfo > div ul li span:nth-child(1) {
width: 40%;
}
.equipmentLabel .labelInfo > div ul li span:nth-child(2) {
width: 15%;
color: #f0c002;
text-align: right;
margin-right: 10px;
}
.equipmentLabel .labelInfo > div ul li span:nth-child(3) {
width: 30%;
}
</style>
<!-- 引入echarts文件 -->
<script src="./assets/js/echarts.min.js"></script>
</head>
<body style="background-color: #030B1A;">
<div class="header-box">
<div class="titile">智慧风机监测系统</div>
</div>
<div class="echarts-box"></div>
<!-- 我们将把three.js渲染的效果显示在这个div中div为容器 -->
<div id="puiedu-webgl-output" ></div>
<!-- 2D信息弹窗 -->
<ul id='equipmentLabelRef' class='equipmentLabel' >
<li></li>
<li class='labelInfo' >
<div>
<header>
<div id="cn">{labelData.cn}</div>
<span id="en">{labelData.en}</span>
</header>
<ul id="valueUl"></ul>
</div>
</li>
</ul>
<script type="module">
import * as THREE from "../src/Three.js"
import { GLTFLoader } from '../jsm/loaders/GLTFLoader.js';
import { OrbitControls } from '../jsm/controls/OrbitControls.js';
//后处理js
// EffectComposer(效果组合器)对象
import { EffectComposer } from '../jsm/postprocessing/EffectComposer.js'
// RenderPass该通道在指定的场景和相机的基础上渲染出一个新场景
import { RenderPass } from '../jsm/postprocessing/RenderPass.js'
import { OutlinePass } from '../jsm/postprocessing/OutlinePass.js'
//2D信息
import {CSS2DRenderer,CSS2DObject} from "../jsm/renderers/CSS2DRenderer.js";
//导入模拟数据
import {labelData} from './assets/labelData/labelData.js'
/*
*定义变量
*/
const scale = 0.0003
let size = {
w: window.innerWidth,
h: window.innerHeight
}
//存储各个部件
let equipmentMaterialMap = new Map()
//风机涡轮转动参数
let matrixTurbine = null
//颜色材质
let metal = null
//线框材质
let wireframe = null
//动画帧
let mixers = new Map()
//创建一个动画时钟对象Clock
let clock = new THREE.Clock()
let mixer = null
/*
*点击事件相关变量
*/
//获取所有内部结构网格对象,用于判断射线相交对象
let equipment = null
//鼠标点击-屏幕坐标
let mouse = new THREE.Vector2();
//用于检测的射线
let raycaster = new THREE.Raycaster();
//效果组合
let compose = null
//弹出信息框总数据
let equipmentLabelData = labelData()[0]
//当前选中模型的数据
let nowLabelData = null
let labelCSS2D = null
2022-10-17 08:56:53 +08:00
var scene = null
var camera = null
var renderer = null
let labelRenderer = null
2022-10-14 16:50:20 +08:00
2022-10-17 08:56:53 +08:00
//基本组件准备
initBaseElement()
//导入模型
loadModel()
2022-10-14 16:50:20 +08:00
//执行渲染
2022-10-17 08:56:53 +08:00
run()
/*
*基本组件准备(场景、相机、渲染器、灯光)
*/
function initBaseElement(){
//创建场景对象【场景:是一个容器,主要用于保存、跟踪所要渲染的物体和使用的光源】
scene = new THREE.Scene()
//创建相机【摄像机:决定了能够在场景中看到什么】
camera = createdCamera(30, size.w, size.h, [-4, -3.5, 4], 1)
//创建光源
createdLight()
//创建渲染器【渲染器:会基于摄像机的角度来计算场景对象在浏览器中会渲染成什么样子】
renderer = createdRender()
}
/*
*导入模型及渲染元素(地板、风机内部结构、风机外部涡轮)
*/
function loadModel(){
//加载地板
loadPlane()
//加载内部结构
loadEquipment()
//加载外壳涡轮
loadTurbine()
//2D信息窗注入3D场景
labelRenderer = createTurbineLabel()
}
/*
*执行(执行渲染、鼠标操作)
*/
function run(){
// //辅助坐标系 参数250表示坐标系大小可以根据场景大小去设置
// var axesHelper = new THREE.AxesHelper(250)
// scene.add(axesHelper)
//执行渲染
render()
//鼠标操作
mouseMove()
//浏览器窗口监听,实现场景的自适应
window.addEventListener('resize',onWindowResize,false)
}
2022-10-14 16:50:20 +08:00
/*
*创建相机
*/
function createdCamera(fov,width,height,position,zoom){
var camera = new THREE.PerspectiveCamera(fov,width/height,0.1,1000,zoom)
camera.position.set(position[0],position[1],position[2])
return camera
}
/*
*创建光源
*/
function createdLight(){
const arr = [
[100,100,100],
[-100,100,100],
[100,-100,100]
]
arr.forEach(lightArr=>{
let spotLight = new THREE.DirectionalLight(0xffffff,3)
let [x,y,z] = lightArr
spotLight.position.set(x, y, z);
scene.add(spotLight);
})
}
//渲染器
function createdRender() {
let renderer = new THREE.WebGLRenderer({antialias: true, alpha: true})
renderer.shadowMap.enabled = true
renderer.setSize(size.w,size.h)
renderer.setClearColor(0x030B1A)
//获取容器,并加入渲染器
document.getElementById('puiedu-webgl-output').appendChild(renderer.domElement)
return renderer
}
//加载地板
function loadPlane(){
const loader = new GLTFLoader();
loader.load('assets/models/plane.glb',(obj)=>{
let mesh = obj.scene
mesh.scale.set(scale,scale,scale)
mesh.position.set(0,-2,0)
scene.add(mesh)
})
}
//加载内部结构
function loadEquipment(){
const loader = new GLTFLoader();
loader.load('assets/models/equipment.glb',(obj)=>{
let mesh = obj.scene
mesh.name = 'equipment'
//将网格对象给equipment用于鼠标点击时检测与射线相交的物体
equipment = mesh
console.log("内部结构equipment",equipment)
mesh.traverse(child =>{
if (child.isMesh) {
child.material = child.material.clone()
equipmentMaterialMap.set(child.name,child)
}
})
mesh.scale.set(scale,scale,scale)
mesh.position.set(0,-2,0)
scene.add(mesh)
})
}
//加载外壳涡轮
function loadTurbine(){
const loader = new GLTFLoader();
if (scene.getObjectByName('turbine')) {
let removeTurbine = scene.getObjectByName('turbine')
scene.remove(removeTurbine)
}
loader.load('assets/models/turbine.glb',(obj)=>{
console.log("模型",obj)
matrixTurbine = obj
let mesh = obj.scene
mesh.name = 'turbine'
metal = mesh.getObjectByName('颜色材质')
wireframe = mesh.getObjectByName('线框材质')
//设置颜色材质的显示和隐藏
metal.visible = false
mesh.scale.set(scale, scale, scale)
mesh.position.set(0,-2,0)
scene.add(mesh)
changeAnimation(mesh,'Anim_0')
})
}
//风机旋转动画
function changeAnimation(turbine,animationName){
const animations = matrixTurbine.animations
//创建混合器播放turbine包含的帧动画数据
mixer = new THREE.AnimationMixer(turbine)
//AnimationClip 是一组可重复使用的关键帧轨迹,代表一个动画
const clip = THREE.AnimationClip.findByName(animations,animationName)
const key = "AA"
if (clip) {
const action = mixer.clipAction(clip)
action.play()
mixers.set(key, mixer)
}else{
mixers.delete(key)
}
}
//鼠标点击事件
function onPointerClick(event) {
const [w,h] = [window.innerWidth,window.innerHeight]
mouse.x = (event.clientX / w) * 2 - 1
mouse.y = -(event.clientY / h) * 2 + 1
//更新射线
raycaster.setFromCamera(mouse, camera)
console.log('参数',equipment)
//返回被击中的信息
const intersects = raycaster.intersectObject(equipment, true)
if (intersects.length <= 0) {
return false;
}
const selectedObject = intersects[0].object
if (selectedObject.isMesh) {
outline([selectedObject])
//显示弹窗
updateLabel(intersects[0],equipmentLabelData[selectedObject.name])
}
}
//点击后高亮线框渲染
function outline(selectedObjects,color = 0x15c5e8) {
const [w,h] = [window.innerWidth,window.innerHeight]
//RenderPass这个通道会渲染场景但不会将渲染结果输出到屏幕上
let renderPass = new RenderPass(scene,camera)
//高亮数据
let outlinePass = new OutlinePass(
new THREE.Vector2(w,h),//分辨率
scene,
camera,
selectedObjects//选中的物体对象,传入需要边界线进行高亮处理的对象
)
outlinePass.renderToScreen = true
outlinePass.selectedObjects = selectedObjects
outlinePass.edgeStrength = 3//粗
outlinePass.edgeGlow = 0//发光
outlinePass.visibleEdgeColor.set(color);//设置显示的颜色
outlinePass.hiddenEdgeColor.set(color);//设置隐藏的颜色
//创建效果组合器对象,可以在该对象上添加后期处理通道,通过配置该对象,
//使它可以渲染我们的场景并应用额外的后期处理步骤在render循环中
//使用EffectComposer渲染场景、应用通道并输出结果
compose = new EffectComposer(renderer)
compose.addPass(renderPass)
compose.addPass(outlinePass)
compose.render(scene,camera)
}
//创建2D信息窗
function createTurbineLabel(){
let labelRenderer = new CSS2DRenderer();
labelRenderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild( labelRenderer.domElement );
labelCSS2D = new CSS2DObject(document.getElementById('equipmentLabelRef'));
scene.add(labelCSS2D);
labelCSS2D.visible = false
return labelRenderer
}
//更新信息窗位置
function updateLabel(intersect,labelData){
var cnDiv = document.getElementById('cn')
cnDiv.innerHTML = labelData.cn
var enDiv = document.getElementById('en')
enDiv.innerHTML = labelData.en
var str = ""
labelData.list.forEach(item=>{
str+= `<li>
<span>${item.name}</span>
<span>${item.value}</span>
<span>${item.unit}</span>
</li>`
})
document.getElementById('valueUl').innerHTML=str
const point = intersect.point;
labelCSS2D.position.set(point.x-0.01, point.y+0.01, point.z-0.03);
labelCSS2D.visible = true
}
/*
*执行渲染
*/
function render(){
//执行渲染操作
if (scene && camera) {
renderer.render(scene,camera)
labelRenderer.render(scene,camera)
}
// 用于跟踪时间的对象
const delta = new THREE.Clock().getDelta(); //获取自设置 oldTime 时间以来经过的秒数,并将 oldTime 设置为当前时间在此delta基本为0
if (compose) {
compose.render(delta);
}
requestAnimationFrame(render)
//获得两帧的时间间隔
const mixerUpdateDelta = clock.getDelta();
//更新混合器相关的时间
mixers.forEach(mixer =>{
mixer.update(mixerUpdateDelta)
})
}
/*
*鼠标操作-放大、缩小、旋转
*/
function mouseMove(){
//创建控件对象
var controls = new OrbitControls(camera,labelRenderer.domElement)
//初始化鼠标点击事件
document.addEventListener("click", onPointerClick);
}
/*
*三维场景自适应
*/
function onWindowResize(){
camera.aspect = window.innerWidth/window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth,window.innerHeight)
labelRenderer.setSize( window.innerWidth, window.innerHeight );
}
</script>
<script type="module">
//初始化echarts实例
var myChart = echarts.init(document.querySelector(".echarts-box"))
//制定图表的配置项和数据
var options = {
title: {
text: '风机功率和风速折线图',
textStyle:{
color:'#fffff0',
fontSize : 16,
},
left : 5
},
tooltip: {
trigger: "axis",
},
legend: {
textStyle:{
color:'#ffffff'
},
data: ['功率', '风速']
},
grid: {
left: 10,
right: 10,
bottom: 20,
top: 30,
containLabel: true,
},
xAxis: {
name: "时间/小时",
type: "category",
// boundaryGap: false,
data: ["1", "3", "5", "7", "9", "11", "13","15", "17", "19", "21", "23"],
axisLine: {
// show: false,
lineStyle: {
color: "#028ab5ad",
},
},
},
yAxis: {
// name: "风速/功率",
type: "value",
axisLine: {
show: false,
lineStyle: {
color: "#028ab5ad",
},
},
splitLine: {
lineStyle: {
color: ["#028ab545"],
},
},
},
series: [
{
name: "功率",
type: "line",
data: [12, 6, 13, 5, 18, 15, 8,2, 4, 12, 15, 10],
lineStyle: {
color: "#15c5e8",
},
itemStyle: {
normal: {
color: "#15c5e8",
},
},
},
{
name: "风速",
type: "line",
// stack: "总量",
data: [2, 4, 12, 15, 10, 11, 5,12, 6, 13, 5, 18],
lineStyle: {
color: "#c8a818",
},
itemStyle: {
normal: {
color: "#c8a818",
},
},
},
],
}
//使用刚指定的配置项和数据显示图表
myChart.setOption(options)
</script>
</body>
</html>