基於HTML5快速搭建3D機房設備面板


以真實設備為模型,搭建出設備面板,並實時獲取設備運行參數,顯示在設備面板上,這相比於純數值的設備監控系統顯得更加生動直觀。今天我們就在HT for Web的3D技術上完成設備面板的搭建。

我們今天模擬的設備是機房設備,先來目睹下最終效果:http://www.hightopo.com/demo/blog_3d_20150810/server.html

我來解釋下這個模型,一個帶有透明玻璃門的機櫃,機櫃里裝有5台設備,門可以開合,設備可以插拔,那么我么該如何搭建這樣的設備呢?方法不難,我們一步一步來。

我們先從設備開始,設備的示意圖如下:

看起來有模有樣的,其實呢,它就是一個長方體,然后在長方體的正面貼上一張圖片,這樣子設備的殼就出來了,創建代碼如下:

var node = createNode([0, 0, 0], [475, 100, 0]);
node.s({
    'front.image': 'panel',
    'all.color': '#E6DEEC'
});
node.setToolTip('Double click to pop the server’);

 

其中設置設備的正面圖片的方法是通過設置節點的front.image樣式屬性來實現的,在代碼中將front.image屬性設置為’panel’,而’panel’屬性是已經通過ht.Default.setImage()方法注冊了的圖片的別名,在代碼中還設置了長方體各個面的顏色和鼠標懸停時的提示語。

在代碼中還調用了createNode()的方法,該方法並沒有做什么特殊的操作,只是將創建3D拓撲節點的代碼封裝起來,精簡代碼,避免相同的代碼重復書寫,具體的封裝如下:

/**
 * 創建3D拓撲節點,並添加到dataModel中
 * @param p3 {array} 位置信息
 * @param s3 {array} 長寬高信息
 * @returns {ht.Node} 3D拓撲節點
 */
function createNode(p3, s3) {
    var node = new ht.Node();
    node.s({
        'shape' : 'rect'
    });
    node.p3(p3);
    node.s3(s3);
    dataModel.add(node);
    return node;
}

該方法通過傳入位置信息和大小信息創建出一個3D拓撲節點,並添加到dataModel中,最后返回該節點對象。

剛剛我們只是創建了設備的外殼而已,在設備上又部分端口是被被占用的,所以接下來我們要做的就是填充設備端口,仔細看了下設備的端口形狀,發現形狀是不規則的呢,那么設備端口該如何填充呢?我們只需要找一個和端口形狀一樣的圖片貼在長方體的正面,然后套在設備上就可以了,具體的實現如下:

/**
 * 創建端口節點,並吸附到指定的節點上
 * @param indexes {array} 端口位置信息
 * @param host {ht.Node} 被吸附的節點對象
 */
function createPort(indexes, host) {
    var p3 = host.p3(),
        s3 = host.s3(),
        x = -s3[0] / 2,
        y = 100 / 2 + p3[1],
        z = 1 + s3[2] / 2;
    indexes.forEach(function(index) {
        var port = new ht.Node();
        port.setName('Port');
        port.s({
            'front.image' : 'port_green',
            'all.light' : true
        });
        port.setHost(host);
        port.setSize3d(28, 23, 10);
        if (index % 2 === 0) {
            var col = (index - 2) / 2;
            port.setPosition3d(x + 67.5 + col * 32, y - 60.5, z);
            port.s({
                'front.uv' : [0, 1, 0, 0, 1, 0, 1, 1]
            });
        }
        else {
            var col = (index - 1) / 2;
            port.setPosition3d(x + 67.5 + col * 32, y - 26.5, z);
        }
        dataModel.add(port);
    });
}

在設備上總共有20個端口,我們通過傳入的端口位置信息來確定創建出來的節點位置,仔細觀察設備端口可以發現,第二排的端口和第一排的端口方向不一樣,所以在創建第二排的端口時需要通過設置front.uv屬性來控制貼圖的翻轉,當然貼圖也是我們事先注冊好了的。

好了,到這里我們的設備模型就構建出來了,那么接下來就是創建機櫃了,機櫃的創建就和設備外殼的創建基本相似,不一樣的地方在於,機櫃有一個門,這個門有開合的功能,由於拓撲節點無法單獨對節點的某一面分離出來做旋轉操作,所以門必須是一個單獨的拓撲節點,我們先來看看機櫃的效果圖:

效果圖種,我們把門稍微裝飾了一下,在門的邊緣上加上了藍色的貼邊,讓門看起來更有質感,效果圖和思路都有了,代碼自然而然就出來了,瞧瞧下面的代碼,有一點點小復雜哦。

var h = 1000, w = 477, d = 400, k = 20;
// 創建機櫃
var main = createNode([0, 0, 0], [w, h, d]);

main.s({
    'all.color' : '#403F46',
    'front.visible' : false
});

// 創建門
var face = new ht.Shape(),
    s = {'all.visible' : false, 'front.visible' : true};

dataModel.add(face);
face.addPoint({x : -w / 2, y : d / 2 - 1});
face.addPoint({x : w / 2, y : d / 2 - 1});
face.addPoint({x : w + w / 2, y : d / 2 - 1});
face.setSegments([1, 2, 1]);
face.setTall(h);
face.setThickness(1);
face.s(s);
face.setHost(main);

face.s({
    'all.color' : 'rgba(0, 40, 60, 0.7)',
    'all.reverse.flip' : true,
    'all.transparent' : true,
    '3d.movable' : false
});
face.face = true;
face.open = false;
face.angle = -Math.PI * 0.6;
face.setToolTip('Double click to open the door');

// 創建門的貼邊
var up = createNode([0, h / 2 - k / 2, d / 2], [w, k, 1], false, false).s(s),
    down = createNode([0, -h / 2 + k / 2, d / 2], [w, k, 1], false, false).s(s),
    right = createNode([w / 2 - k / 2, 0, d / 2], [k, h, 1], false, false).s(s),
    left = createNode([-w / 2 + k / 2, 0, d / 2], [k, h, 1], false, false).s(s);

up.setHost(face);
down.setHost(face);
left.setHost(face);
right.setHost(face);

代碼的邏輯是這樣的,先創建一個長方體作為機櫃的外殼,然后將長方體的正面設置為隱藏,然后創建一個多邊形作為門,將門設為淺藍色半透明,最后創建4個藍色長方體貼到門的邊緣作為裝飾,如此一個機櫃就搭建完成了。

設備模型有了,機櫃有了,接下來的工作就是將兩者合並起來,方法很簡單,就是創建設備並將設備吸附到機櫃上,具體的代碼如下:

var num = 5,
    start = 400;
for (var i = 0; i < num; i++) {
    var y = start - 170 * i,
        z = (d - 30) / 2;
    var node = createNode([0, y, 0], [w - 2, 100, d - 30]);
    node.s({
        'front.image' : 'panel',
        'all.color' : '#E6DEEC',
        'all.reverse.cull' : true,
        '3d.movable' : false
    });
    node.pop = false;
    node.setToolTip('Double click to pop the server');
    node.setHost(main);
    createPort([1, 2, 3, 4, 5, 6, 7, 13, 16, 17, 20], node);
}

還記得前面構建設備模型和機櫃門的代碼中,我們對這兩個模型添加了鼠標懸停時的提示內容,雙擊可以打開門,雙擊可以抽出設備,那么我們現在就來實現這兩個效果,首先我們來分析下具體的實現方案:

雙擊的事件要添加在哪里呢?對每個拓撲節點做監聽嗎?這是最直接的方法,但是這樣做的話,有多少節點將會有多少個對應的處理函數,而且同一類型的處理函數又是一樣的,那么這就會導致系統資源的浪費,所以對每個節點做雙擊的監聽方案是不可取的,那么我們該如何處理雙擊事件呢?我們可以換個角度思考,所有的節點都是添加到3D拓撲組件上的,那么我們是否可以通過監聽3D拓撲組件的雙擊事件,然后通過事件對象獲取到對應的節點,然后通過判斷節點上設置的自定義標識屬性來做相應的處理,具體的代碼如下:

// 監聽3D拓撲組件的dataDoubleClicked事件
g3d.onDataDoubleClicked = function(data, e, dataInfo) {
    // 若果節點為門
    if (data.face) {
        // 遍歷所有吸附在機櫃下的節點
        data.getHost().getAttaches().each(function(attach) {
            // 如果節點狀態為彈出,則調用函數還原節點位置
            if (attach.pop) {
                toggleData(attach);
            }
        });
    }
    // 如果節點為端口節點,則觸發其所吸附設備的雙擊事件
    else if (data.a('port')) {
        toggleData(data.getHost());
        return;
    }
    toggleData(data);
};

閱讀上面的代碼,大家會發現實現的方案和我們提到的方案不太一樣,我們通過監聽3D拓撲組件的dataDoubleClicked事件就可以直接獲取到被雙擊的節點對象,而無需我們自己通過事件對象獲取對應的節點對象,當然就監聽dataDoubleClicked事件了。

在代碼中,我們調用了toggleData()方法來處理雙擊事件,具體的處理代碼如下:

/**
 * 節點雙擊處理函數
 * @param data {ht.Node} 被雙擊的節點
 */
function toggleData(data) {
    var angle = data.angle,
        pop = data.pop;

    // 當前雙擊的對象為門
    if (angle != null) {
        if (anim) {
            anim.stop(true);
        }
        var oldAngle = data.getRotation();
        if (data.open) {
            angle = -angle;
        }
        data.open = !data.open;
        anim = ht.Default.startAnim({
            action : function(t) {
                data.setRotation(oldAngle + t * angle);
            }
        });
    }
    // 當前雙擊的對象為設備
    else if (pop != null) {
        if (anim) {
            anim.stop(true);
        }
        var p3 = data.p3(),
            s3 = data.s3(),
            dist = (pop ? -s3[2] : s3[2]) / 2;
        data.pop = !data.pop;
        anim = ht.Default.startAnim({
            action : function(t) {
                data.p3(p3[0], p3[1], p3[2] + t * dist);
            }
        });
    }
}

在代碼中,根據節點預設的不同的屬性值來確認該做什么處理,如果節點對象是門的話,則通過ht.Default.startAnim()方法緩慢的修改門的rotation;如果節點對象是設備的話,則緩慢修改設備的position。

到這里,今天的Demo的所有表現和功能就完成了,今天的內容中有設計到節點的style應用,我沒有做深入的講解,后續會給大家一一介紹,感興趣的朋友可以通過HT for Web樣式手冊來了解更多的內容。

下面附上今天的Demo源碼及視頻。

我已經把今天的Demo上傳至官網了,感興趣的朋友可以點擊這里訪問。

http://www.hightopo.com/demo/blog_3d_20150810/server.html

http://v.youku.com/v_show/id_XMTMwNTY2ODE0NA==.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM