首頁  >  文章  >  web前端  >  HTML5 WebGL實現的3D機房範例

HTML5 WebGL實現的3D機房範例

小云云
小云云原創
2018-03-19 10:06:314451瀏覽

用WebGL 渲染的3D 機房現在也不是什麼新鮮事了,這篇文章的主要目的是說明一下,3D 機房中的eye 和center 的問題,剛好在項目中用上了,好生思考了一番,最後覺得這個例子最符合我的要求,就拿來當記錄。

效果圖

這個3D 機房的Demo 做的還不錯,比較美觀,基礎的互動也都滿足,接下來看看怎麼實現。

程式碼產生

定義類別

#首先從index.html 中呼叫的js 路徑順序一個一個開啟對應的js,server.js 中自訂了一個Editor.Server 類別由HT 封裝的ht.Default.def 函式所建立的(注意,建立的類別名稱Editor.Server 前面的Editor 不能用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);
    }
});

建立Editor.Server 類別

這個類別可以建立一個ht.Node 節點,並設定節點的顏色和前面貼圖:


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 //设置节点正面的图片
    });
};

這樣我在需要建立伺服器元件的位置直接new 一個新的伺服器元件物件即可,並且能夠直接呼叫我們上面聲明的setHost 等函數,很快我們就會用上。

接下來建立Editor.Cabinet 機櫃類,方法跟上面Editor.Server 類別的定義方法差不多:


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 坐标
    }
});

建立Editor.Cabinet 類別

這個類別相對於前面的Editor.Server 伺服器元件類別要相對複雜一點,這個類別中創建了一個櫃身、櫃門以及機櫃內部的伺服器元件:


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 节点
    });
};

上面程式碼中唯一沒提到的是Editor.randomList 函數,這個函數是在global.js 檔案中宣告的,宣告如下:


#
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;
    }
};

好了,場景中的各個部分的類別都創建完成,那我們就該將場景創建起來,然後將這些圖元都堆進去!

場景建立

如果熟悉的同學應該知道,用HT 建立一個3D 場景只需要new 一個3D 元件,再將透過addToDOM 函數將這個場景加入body 中即可:


var g3d = E.main = new ht.graph3d.Graph3dView(); //3d 场景

main.js 檔案中主要做的是在3D 場景中一些必要的元素,例如牆面,地板,門,空調以及所有的機櫃的生成和排放位置,還有非常重要的互動部分。

牆體,地板,門,空調和機櫃的創建我就不貼代碼出來了,有興趣的請自行查看代碼,這裡主要說一下雙擊機櫃以及與機櫃有關的任何物體(櫃門,伺服器設備)則3D 中camera 的視線就會移動到雙擊的機櫃的前方某個位置,而且這個移動是非常順滑的,之前技藝不精,導致這個部分想了很久,最後參考了這個Demo 的實現方法。

為了能夠重複設定eye 和center,將設定這兩個參數對應的內容封裝為setEye 和setCenter 方法,setCenter 方法與setEye 方法類似,這裡不重複贅述:


#
// 设置眼睛位置
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
            ]);
        }
    });
};

我沒有重複聲明setCenter 函數不代表這個函數不重要,恰恰相反,這個函數在「視線」移動的過程中起到了決定性的作用,上面的setEye 函數相當於我想走到我的目標位置的前面(至少我定義的時候是這種用途),而sCenter 的定義則是將我的視線移到了目標的位置(比如我可以站在我現在的位置看我右後方的物體,也可以走到我右後方去,站在那個物體前面看它),這點非常重要,請大家好好品味一下。

雙擊事件倒是簡單,只要監聽HT 封裝好的事件,判斷事件類型,並作出對應的動作即可:


g3d.mi(function(e) {//addInteractorListener 事件监听函数
    if (e.kind !== &#39;doubleClickData&#39;)  //判断事件类型为双击节点
        return;
    var data = e.data, p3;

    if (data.a(&#39;cabinet&#39;)) //机身
        p3 = data.p3();
    else {
        host = data.getHost(); //获取点击节点的吸附对象
        if (host && host.a(&#39;cabinet&#39;)) {//如果吸附对象为 cabinet
            p3 = host.p3();
        }
    }

    if (!p3) return;

    setCenter(p3); //设置 center 目标的要移向位置为 cabinet 的位置
    setEye([p3[0], 211, p3[2] + 247]); //设置 eye 眼睛要移向的位置
});

頂部導覽列

一開始看到這個例子的時候我在想,這人好厲害,我用HT 這麼久,用HT 的ht.widget .Toolbar 還沒能做出這麼漂亮的效果,看著看著發現這原來是用form 表單做的,厲害,我真是太愚鈍了。


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: &#39;./symbols/inputBG.json&#39;,
        stretch: &#39;centerUniform&#39;
    }
}], [40, 260]);//第二个参数为每个元素宽度信息数组,宽度值大于1代表固定绝对值,小于等于1代表相对值,也可为80+0.3的组合
form.addRow([null, null, {
    id: &#39;searchInput&#39;,
    textField: {}
}, {
    element: &#39;机房可视化管理系统&#39;,
    color: &#39;white&#39;,
    font: &#39;18px arial, sans-serif&#39;
}, null, {
    button: {
        // label: &#39;视图切换&#39;,
        icon: &#39;./symbols/viewChange.json&#39;,
        background: null,
        selectBackground: &#39;rgb(128,128,128)&#39;,
        borderColor: &#39;rgba(0, 0, 0, 0)&#39;,
        onClicked: function() {
            E.focusTo();
        }
    }
}, null, {
    button: {
        // label: &#39;告警&#39;,
        icon: &#39;./symbols/alarm.json&#39;,
        togglable: true,
        selected: false,
        background: null,
        selectBackground: &#39;rgb(128,128,128)&#39;,
        borderColor: &#39;rgba(0, 0, 0, 0)&#39;,
        onClicked: function(e) {
            E.setAlarmVisible(this.isSelected());
        }
    }
}, null], [40, 42, 218, 300, 0.1, 50, 10, 50, 10]);

以上都只是能實現,但是並沒有真正地添加進 html 標籤中,也就意味著,現在介面上什麼都沒有!別忘了在頁面載入的時候將3D 場景加入到body 中,同時也別忘了將form 表單加入到body 中,並且設定視窗大小變化事件時,form 表單也需要即時更新:


window.addEventListener(&#39;load&#39;, function() {
    g3d.addToDOM(); //将 3D 场景添加进 body 中

    document.body.appendChild(E.top.getView()); //将 form 表单组件底层 p 添加进 body 中

    window.addEventListener(&#39;resize&#39;, 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 = &#39;0&#39;;//HT 默认将所有的组件底层p的position设置为absolute
    style.right = &#39;0&#39;;
    style.top = &#39;0&#39;;
    style.bottom = &#39;0&#39;;      
    window.addEventListener(&#39;resize&#39;, function () { self.iv(); }, false); //窗口大小变化监听事件,通知组件变化更新          
}

这样,所有的代码就结束了,可以自己右键“检查”,network 中可以获取相对应的 json 文件。

相关推荐:

打造最美HTML5 3D机房(MONO哥强势归来,第四季惊艳发布)_html/css_WEB-ITnose

以上是HTML5 WebGL實現的3D機房範例的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn