Home >Web Front-end >H5 Tutorial >Example of 3D computer room implemented by HTML5 WebGL
The 3D computer room rendered with WebGL is nothing new now. The main purpose of this article is to explain the problem of eye and center in the 3D computer room, which happened to be used in the project. I thought about it for a while. Finally, I felt that this example best met my requirements, so I used it as a record.
Rendering
The demo of this 3D computer room is pretty good, quite beautiful, and the basic interactions are satisfactory. Come on and see how to do it.
Code generation
Definition class
First open the corresponding js paths one by one from index.html js, server.js has customized an Editor.Server class created by the ht.Default.def function encapsulated by HT (note that the Editor in front of the created class name Editor.Server cannot be replaced by E):
ht.Default.def('Editor.Server', Object, {//第一个参数为类名,如果为字符串,自动注册到HT的classMap中;第二个参数为此类要继承的父类;第三个参数为方法和变量的声明 addToDataModel: function(dm) { //将节点添加进数据容器 dm.add(this._node);// ht 中的预定义函数,将节点通过 add 方法添加进数据容器中 }, setHost: function() { //设置吸附 this._node.setHost.apply(this._node, arguments); }, s3: function() {//设置节点的大小 this._node.s3.apply(this._node, arguments); }, setElevation: function() {//控制Node图元中心位置所在3D坐标系的y轴位置 this._node.setElevation.apply(this._node, arguments); } });
Create the Editor.Server class
This class can create an ht.Node node and set the node's color and front texture :
var S = E.Server = function(obj) {//服务器组件 var color = obj.color, frontImg = obj.frontImg; var node = this._node = new ht.Node();//创建节点 node.s({//设置节点的样式 s 为 setStyle 的缩写 'all.color': color,//设置节点六面的颜色 'front.image': frontImg //设置节点正面的图片 }); };
In this way, I can directly create a new server component object where I need to create the server component, and I can directly call the setHost and other functions we declared above, which is very convenient We'll need it soon.
Next, create the Editor.Cabinet cabinet class. The method is similar to the definition method of the Editor.Server class above:
##
ht.Default.def('Editor.Cabinet', Object, { addToDataModel: function(dm) { dm.add(this._door); dm.add(this._node); this._serverList.forEach(function(s) { s.addToDataModel(dm); }); }, p3: function() { this._node.p3.apply(this._node, arguments);//设置节点的 3d 坐标 } });Create the Editor.Cabinet class This class is relatively more complex than the previous Editor.Server server component class. This class creates a cabinet body, cabinet door, and server components inside the cabinet:
var C = E.Cabinet = function(obj) { var color = obj.color, doorFrontImg = obj.doorFrontImg, doorBackImg = obj.doorBackImg, s3 = obj.s3; var node = this._node = new ht.Node(); // 柜身 node.s3(s3);//设置节点的大小 为 setSize3d node.a('cabinet', this);//自定义 cabinet 属性 node.s({//设置节点的样式 为 setStyle 'all.color': color,//设置节点六面的颜色 'front.visible': false//设置节点前面是否可见 }); if (Math.random() > 0.5) { node.addStyleIcon('alarm', {//向节点上添加 icon 图标 names: ['icon 温度计'],//包含多个字符串的数组,每个字符串对应一张图片或矢量(通过ht.Default.setImage注册) face: 'top',//默认值为front,图标在3D下的朝向,可取值left|right|top|bottom|front|back|center position: 17,//指定icons的位置 autorotate: 'y',//默认值为false,图标在3D下是否自动朝向眼睛的方向 t3: [0, 16, 0],//默认值为undefined,图标在3D下的偏移,格式为[x,y,z] width: 37,//指定每个icon的宽度,默认根据注册图片时的宽度 height: 32,//指定每个icon的高度,默认根据注册图片时的高度 textureScale: 4,//默认值为2,该值代表内存实际生成贴图的倍数,不宜设置过大否则影响性能 visible: { func: function() { return !!E.alarmVisible; }}//表示该组图片是否显示 }); } var door = this._door = new ht.DoorWindow();//柜门 door.setWidth(s3[0]);//置图元在3D拓扑中的x轴方向的长度 door.setHeight(1);//设置图元在3D拓扑中的z轴长度 door.setTall(s3[1]);//控制Node图元在y轴的长度 door.setElevation(0);//设置图元中心在3D坐标系中的y坐标 door.setY(s3[2] * 0.5);//设置节点在 y 轴的位置 door.setHost(node);//设置吸附 door.s({//设置节点样式 setStyle 'all.color': color,//设置节点六面颜色 'front.image': doorFrontImg,//设置节点正面图片 'front.transparent': true,//设置节点正面是否透明 'back.image': doorBackImg,//设置节点背面的图片 'back.uv': [1,0, 1,1, 0,1, 0,0],//自定义节点后面uv贴图,为空采用默认值[0,0, 0,1, 1,1, 1,0] 'dw.axis': 'right'//设置DoorWindow图元展开和关闭操作的旋转轴,可取值left|right|top|bottom|v|h }); var serverList = this._serverList = []; var max = 6, list = E.randomList(max, Math.floor(Math.random() * (max - 2)) + 2); //global.js 中声明的获取随机数的函数 var server, h = s3[0] / 4; list.forEach(function(r) { var server = new E.Server({ //服务器组件 color: 'rgb(51,49,49)', frontImg: '服务器 组件精细' }); server.s3(s3[0] - 2, h, s3[2] - 4);//设置节点大小 server.setElevation((r - max * 0.5) * (h + 2));//设置节点中心点在 y 轴的坐标 server.setHost(node);//设置节点的吸附 serverList.push(server);//向 serverList 中添加 server 节点 }); };The only thing not mentioned in the above code is the Editor.randomList function. This function is declared in the global.js file and is declared as follows:
var E = window.Editor = { leftWidth: 0, topHeight: 40, randomList: function(max, size) { var list = [], ran; while (list.length < size) { ran = Math.floor(Math.random() * max); if (list.indexOf(ran) >= 0) continue; list.push(ran); } return list; } };Okay, now that the classes for each part of the scene have been created, we should create the scene and then pile these primitives into it!
Scene creation
If you are familiar with it, you should know that using HT to create a 3D scene only requires a new 3D component, and then add the scene through the addToDOM function. Just in the body:var g3d = E.main = new ht.graph3d.Graph3dView(); //3d 场景main.js file mainly does some necessary elements in the 3D scene, such as walls, floors, doors, air conditioners and all The generation and discharge position of the cabinets, as well as the very important interactive part. I will not post the code for creating walls, floors, doors, air conditioners and cabinets. If you are interested, please check the code yourself. Here I mainly talk about double-clicking the cabinet and any objects related to the cabinet (cabinet door) , server equipment), the camera's line of sight in 3D will move to a position in front of the double-clicked cabinet, and this movement is very smooth. My previous skills were not good, so I thought about this part for a long time, and finally referred to the implementation of this Demo method. In order to be able to repeatedly set eye and center, the content corresponding to setting these two parameters is encapsulated into the setEye and setCenter methods. The setCenter method is similar to the setEye method and will not be repeated here:
// 设置眼睛位置 var setEye = function(eye, finish) { if (!eye) return; var e = g3d.getEye().slice(0),//获取当前 eye 的值 dx = eye[0] - e[0], dy = eye[1] - e[1], dz = eye[2] - e[2]; // 启动 500 毫秒的动画过度 ht.Default.startAnim({ duration: 500, easing: easing,//动画缓动函数 finishFunc: finish || function() {}, //动画结束后调用的函数 action: function(v, t) {//设置动画v代表通过easing(t)函数运算后的值,t代表当前动画进行的进度[0~1],一般属性变化根据v参数进行 g3d.setEye([ //设置 3D 场景中的 eye 眼睛的值,为一个数组,分别对应 x,y,z 轴的值 e[0] + dx * v, e[1] + dy * v, e[2] + dz * v ]); } }); };The fact that I did not repeatedly declare the setCenter function does not mean that this function is not important. On the contrary, this function plays a decisive role in the process of moving the "eyesight". The above setEye function is equivalent to I want to go to the front of my target position (at least this is how I defined it), and the definition of sCenter is to move my sight to the position of the target (for example, I can stand at my current position and look at the object behind me to the right , you can also go to the right behind me and stand in front of the object to look at it), this is very important, please savor it. The double-click event is simple. Just listen to the HT encapsulated event, determine the event type, and take corresponding actions:
g3d.mi(function(e) {//addInteractorListener 事件监听函数 if (e.kind !== 'doubleClickData') //判断事件类型为双击节点 return; var data = e.data, p3; if (data.a('cabinet')) //机身 p3 = data.p3(); else { host = data.getHost(); //获取点击节点的吸附对象 if (host && host.a('cabinet')) {//如果吸附对象为 cabinet p3 = host.p3(); } } if (!p3) return; setCenter(p3); //设置 center 目标的要移向位置为 cabinet 的位置 setEye([p3[0], 211, p3[2] + 247]); //设置 eye 眼睛要移向的位置 });
Top navigation bar
When I first saw this example, I thought, this person is so awesome. I have been using HT for so long, and I use HT’s ht.widget. .Toolbar has not been able to produce such a beautiful effect. After looking at it, I found that it was originally done using a form. It is amazing. I am so stupid.var form = E.top = new ht.widget.FormPane(); //顶部 表单组件 form.setRowHeight(E.topHeight);//设置行高 form.setVGap(-E.topHeight);//设置表单组件水平间距 设置为行高的负值则可以使多行处于同一行 form.setVPadding(0);//设置表单顶部和顶部与组件内容的间距 form.addRow([null, {//向表单中添加一行组件,第一个参数为元素数组,元素可为字符串、json格式描述的组件参数信息、html元素或者为null image: { icon: './symbols/inputBG.json', stretch: 'centerUniform' } }], [40, 260]);//第二个参数为每个元素宽度信息数组,宽度值大于1代表固定绝对值,小于等于1代表相对值,也可为80+0.3的组合 form.addRow([null, null, { id: 'searchInput', textField: {} }, { element: '机房可视化管理系统', color: 'white', font: '18px arial, sans-serif' }, null, { button: { // label: '视图切换', icon: './symbols/viewChange.json', background: null, selectBackground: 'rgb(128,128,128)', borderColor: 'rgba(0, 0, 0, 0)', onClicked: function() { E.focusTo(); } } }, null, { button: { // label: '告警', icon: './symbols/alarm.json', togglable: true, selected: false, background: null, selectBackground: 'rgb(128,128,128)', borderColor: 'rgba(0, 0, 0, 0)', onClicked: function(e) { E.setAlarmVisible(this.isSelected()); } } }, null], [40, 42, 218, 300, 0.1, 50, 10, 50, 10]);The above can only be achieved, but it is not actually added to the html tag, which means that there is nothing on the interface now! Don't forget to add the 3D scene to the body when the page loads, and don't forget to add the form to the body. When setting the window size change event, the form also needs to be updated in real time:
window.addEventListener('load', function() { g3d.addToDOM(); //将 3D 场景添加进 body 中 document.body.appendChild(E.top.getView()); //将 form 表单组件底层 p 添加进 body 中 window.addEventListener('resize', function() {//窗口大小变化事件监听 E.top.iv();//更新 form 表单的底层 p }); });
这里说明一下 addToDOM 函数,对于了解 HT 的机制非常重要。HT 的组件一般都会嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外层的 HT 组件则需要用户手工将 getView() 返回的底层 p 元素添加到页面的 DOM 元素中,这里需要注意的是,当父容器大小变化时,如果父容器是 BorderPane 和 SplitView 等这些 HT 预定义的容器组件,则 HT 的容器会自动递归调用孩子组件invalidate 函数通知更新。但如果父容器是原生的 html 元素, 则 HT 组件无法获知需要更新,因此最外层的 HT 组件一般需要监听 window 的窗口大小变化事件,调用最外层组件 invalidate 函数进行更新。
为了最外层组件加载填充满窗口的方便性,HT 的所有组件都有 addToDOM 函数,其实现逻辑如下,其中 iv 是 invalidate 的简写:
addToDOM = function(){ var self = this, view = self.getView(), style = view.style; document.body.appendChild(view); //将场景的底层 p 添加进 body 中 style.left = '0';//HT 默认将所有的组件底层p的position设置为absolute style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //窗口大小变化监听事件,通知组件变化更新 }
这样,所有的代码就结束了,可以自己右键“检查”,network 中可以获取相对应的 json 文件。
相关推荐:
打造最美HTML5 3D机房(MONO哥强势归来,第四季惊艳发布)_html/css_WEB-ITnose
The above is the detailed content of Example of 3D computer room implemented by HTML5 WebGL. For more information, please follow other related articles on the PHP Chinese website!