基於 HTML5 Canvas 的 3D WebGL 機房建立

對於 3D 機房來講,監控已經不是什麼難事,不一樣的人有不一樣的作法,今天試着用 HT 寫了一個基於 HTML5 的機房,發現果真 HT 簡單好用。本例是將燈光、霧化以及 eye 的最大最小距離等等功能在 3D 機房中進行的一個綜合性的例子。接下來我將對這個例子的實現進行解析,算是本身對這個例子的一個總結吧。整個例子由於沒有設計師的參與,因此樣式上可能比較簡陋,可是在一些細節的地方,好比牆上的貼圖、門框嵌入以及滅火器等等,都是儘量地還原真實的場景,也是花了些心思作這個例子的!html

本例地址:http://www.hightopo.com/guide/guide/core/3d/examples/example_3droom.htmlnode

本例動態圖:canvas

從最基礎的開始,場景的佈置,照 HTML 的思路,這個場景就是將整個頁面放在一個 div 中,再向這個 div 中添加一個頂部的 div 以及一箇中間部分的 div,說實在,若是用 HTML 實現這個步驟,咱們要寫的代碼很少,可是若是要寫幾回這段代碼,想必誰都會以爲煩吧。。。HT 將這種實現方法封裝了起來,整個外部的 div 就是 HT 的基礎組件,這裏咱們用到的是 ht.widget.BorderPane 組件,上部的 div 封裝在 ht.widget.Toolbar 工具條類中,下部分是 3D 部分 ht.graph3d.Graph3dView 組件,將頁面中的兩個部分經過下面的方式添加進 BorderPane 組件中,並將 BorderPane 添加進 body 體中:數組

toolbar = new ht.widget.Toolbar(items);                                                                                                
dataModel = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dataModel);    
g3d.getView().style.background = 'black';
g3d.setGridSize(100);
g3d.setGridGap(100); 
g3d.setFar(30000);
g3d.setOrthoWidth(5000);  
g3d.setFogNear(100);//默認爲1,表明從該距離起物體開始受霧效果影響
g3d.setFogFar(8000);
g3d.reset = reset;
g3d.reset();
g3d.enableToolTip();                                 
borderPane = new ht.widget.BorderPane();
borderPane.setTopView(toolbar);
borderPane.setCenterView(g3d);       

view = borderPane.getView();  
view.className = 'main';
document.body.appendChild(view);
window.addEventListener('resize', function (e) {
    borderPane.invalidate();
}, false);

上面代碼將 borderPane.getView() 添加進 body 體中,是由於 Graph3dView 的界面 DOM 結構是由最底層的 div 元素,以及渲染層 canvas 元素組合而成,經過 getView() 可獲得最底層的 div 元素。服務器

咱們還注意到上面代碼中沒有說起的一個參數 items,這個 items 是 toolbar 工具條的元素,一個數組元素,下面展現一下代碼,這裏簡單地解釋了一下,詳細信息請參考 HT for Web 工具條手冊app

items = [ //工具條中的元素數組
    {
        label: 'White',//工具條元素的標籤文字
        groupId: 'headLightColor',//對工具條元素進行分組,同一個分組內的元素選中會自動出現互斥效果
        selected: true,//工具條元素是否被選中,值爲true或false,對複選框、開關按鈕和單選按鈕有效
        action: function(){// 函數類型,工具條元素被點擊時調用
            g3d.setHeadlightColor('white');
        }
    },        
    {
        label: 'Red',
        groupId: 'headLightColor',
        action: function(){                             
            g3d.setHeadlightColor('red');
        }
    }, 
    {
        label: 'Blue',
        groupId: 'headLightColor',
        action: function(){                             
            g3d.setHeadlightColor('blue');
        }
    },        
    {
        label: 'Yellow',
        groupId: 'headLightColor',        
        action: function(){                             
            g3d.setHeadlightColor('yellow');
        }
    },        
    {
        id: 'step',                        
        unfocusable: true,//工具條元素是否不可獲取焦點,默認鼠標滑過期會顯示一個矩形邊框,可設置爲true關閉此效果
        slider: {
            width: 70,
            step: 500,
            min: 0,
            max: 10000,
            value: 0,                            
            onValueChanged: function(){
                g3d.setHeadlightRange(this.getValue());
            }       
        }                
    },
    'separator', //表示分隔條
    {
        label: 'Fog',
        type: 'check',//工具條元素類型,check表示複選框,toggle表示開關按鈕,radio表示單選按鈕                        
        action: function(){
            g3d.setFogDisabled(!this.selected);
        }
    },
    {
        label: 'White',
        groupId: 'fogColor',
        selected: true,
        action: function(){                             
            g3d.setFogColor('white');
        }
    },                     
    {
        label: 'Red',
        groupId: 'fogColor',
        action: function(){                             
            g3d.setFogColor('red');
        }
    },        
    {
        label: 'Yellow',
        groupId: 'fogColor',        
        action: function(){                             
            g3d.setFogColor('yellow');
        }
    },
    {                       
        unfocusable: true,
        label: 'FogNear',
        slider: {
            width: 70,
            min: 10,
            max: 4000,
            value: 100,                            
            onValueChanged: function(){
                g3d.setFogNear(this.getValue());
            }       
        }                
    },                        
    {                       
        unfocusable: true,
        label: 'FogFar',
        slider: {
            width: 70,
            min: 5000,
            max: 15000,
            value: 8000,                            
            onValueChanged: function(){
                g3d.setFogFar(this.getValue());
            }       
        }                
    },                    
    'separator', 
    {
        id: 'movable',
        label: 'Movable',
        type: 'check',
        selected: false                
    }, 
    {
        label: 'Editable',
        type: 'check',
        selected: false,
        action: function(item){
            g3d.setEditable(item.selected);
        }
    },
    {
        label: 'Wireframe',
        type: 'check',
        selected: false,
        action: function(item){
            if(item.selected){
                dataModel.each(function(data){
                    data.s({
                        'wf.visible': 'selected',
                        'wf.color': 'red'                                        
                    });
                });                                
            }else{
                dataModel.each(function(data){
                    data.s({
                        'wf.visible': false                                       
                    });
                });                               
            }                            
        }
    },
    {
        type: 'toggle',
        label: 'Orthographic Projection',                        
        action: function(item){  
            g3d.setOrtho(item.selected);                           
        }                    
    },
    {
        type: 'toggle',
        selected: false,
        label: 'First Person Mode',
        action: function(item){
            g3d.setFirstPersonMode(item.selected);  
            g3d.reset();
        }
    },                                                       
    'separator',                      
    {
        type: 'check',
        label: 'Origin Axis',
        selected: false,
        action: function(item){
            g3d.setOriginAxisVisible(item.selected);                           
        }
    },   
    {
        type: 'check',
        label: 'Center Axis',
        selected: false,
        action: function(item){
            g3d.setCenterAxisVisible(item.selected);                           
        }
    },                             
    {
        type: 'check',
        label: 'Grid',
        selected: false,
        action: function(item){
            g3d.setGridVisible(item.selected);                           
        }
    },                    
    {
        label: 'Export Image',
        action: function(){                             
            var w = window.open();
            w.document.open();                            
            w.document.write("<img src='" + g3d.toDataURL(g3d.getView().style.background) + "'/>");
                            w.document.close();
        }
    }                    
];

接下來就是建立場景中的各個模型,首先是三個燈光,中間部分一個,左右後方各一個(我將光源標記出來了,看圖~),顏色分別爲綠、紅、藍,以及地板:dom

redLight = new ht.Light();//燈光類
redLight.p3(-1600, 200, -2200);
redLight.s({
    'light.color': 'red',
    'light.range': 1000
});
dataModel.add(redLight);
                
blueLight = new ht.Light();
blueLight.p3(1600, 200, -2200);
blueLight.s({
    'light.color': 'blue',
    'light.range': 1000
});
dataModel.add(blueLight);                                   
                
greenLight = new ht.Light();
greenLight.p3(-800, 400, -200);
greenLight.s({
    'light.center': [-300, 0, -900],
    'light.type': 'spot',
    'light.color': 'green',
    'light.range': 4000,
    'light.exponent': 10
});
dataModel.add(greenLight);                
                
floor = createNode([0, -5, -1500], [5200, 10, 4200]);
floor.s({                             
    'shape3d': 'rect',
    'shape3d.top.color': '#F0F0F0'                    
});  

接下來將場景中間部分的服務器、兩邊的服務器、滅火器,牆上的空調、牆背面的空調等等等等都添加進場景中:ide

for(i=0; i<3; i++){
    for(j=0; j<3; j++){
        createServer1(250+i*280, -1200-500*j, j === 2);
        createServer1(-250-i*280, -1200-500*j, j === 1);
    }
}
                     
//建立2排2的服務器 放在場景的兩邊
for(i=0; i<2; i++){
    for(j=0; j<2; j++){
        createServer2(1500+i*200, -700-500*j, 
                            (i === 1 ? (j === 1 ? '#00FFFF' : '#C800FF') : null));
        createServer2(-1500-i*200, -700-500*j, 
                            (j === 1 ? (i === 1 ? 'red' : 'yellow') : null));
    }
}                                          
          
// fire extinguisher
createFireExtinguisher(1300, -1800, [0.45, 0]); 
createFireExtinguisher(-1300, -1800); 
createFireExtinguisher(1100, -2450);   
createFireExtinguisher(-1100, -2450, [0.45, 0]);  
                
// air condition
createNode([1120, 170, -700], [80, 340, 170]).s({
    'all.color': '#EDEDED',
    'left.image': 'stand'
}).setToolTip('Air Conditioner'); 
createNode([-1120, 170, -700], [80, 340, 170]).s({
    'all.color': '#EDEDED',
    'right.image': 'stand'
}).setToolTip('Air Conditioner'); 
createNode([1680, 400, -1850], [370, 120, 60]).s({
    'all.color': '#767676',
    'front.image': 'air1'
}).setToolTip('Air Conditioner');             
createNode([-1680, 400, -1850], [370, 120, 60]).s({
   'all.color': '#767676',
   'front.image': 'air2'
}).setToolTip('Air Conditioner'); 
for(i=0; i<2; i++){
    createNode([300+i*580, 90, -2630], [260, 180, 60]).s({
        'all.color': '#EDEDED',
        'back.image': 'air3'
    }).setToolTip('Air Conditioner');  
    createNode([-300-i*580, 90, -2630], [260, 180, 60]).s({
        'all.color': '#EDEDED',
        'back.image': 'air3'
    }).setToolTip('Air Conditioner');                      
}

 其中 createServer1 方法是用來建立服務器的方法,由一個 ht.Node 做爲機身以及 ht.Shape 做爲機門組合而成,並在機櫃的內部添加了隨機數臺設備:函數

function createServer1(x, z, disabled){//建立服務器 1(中間部分)
    var h = 360, w = 150, d = 170, k = 10,
         main = createNode([0, 0, 0], [w, h, d]),
         face = new ht.Shape(),
         s = {'all.visible': false, 'front.visible': true};  //設置node節點只有前面可見       
                  
    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);//吸附在main節點上
                
    main.s({
        'all.color': '#403F46',
        'front.visible': false
    });                
                
    if(!disabled){                
        face.s({
            'all.color': 'rgba(0, 40, 60, 0.7)',
            'all.reverse.flip': true,//反面是否顯示正面的東西
            'all.transparent': true//設置爲透明
       });
       face.face = true;//初始化face屬性             
       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]).s(s),
            down = createNode([0, -h/2+k/2, d/2], [w, k, 1]).s(s),
            right = createNode([w/2-k/2, 0, d/2], [k, h, 1]).s(s),
            left = createNode([-w/2+k/2, 0, d/2], [k, h, 1]).s(s);
                                
       up.setHost(face);
       down.setHost(face);
       left.setHost(face);
       right.setHost(face);

       //隨機建立機櫃中的設備數量 2個或者4個
       var num = Math.ceil(Math.random() * 2),
       start = 20 + 20 * Math.random();
       for(var i=0; i<num; i++){
           var node = createNode([0, start+45*i, 0], [w-6, 16, d-30]);
           node.setHost(main);
           node.s({
               'front.image': 'server',
               'all.color': '#E6DEEC',
               'all.reverse.cull': true
           });
           node.pop = false;//初始化設備「彈出」的屬性
           node.setToolTip('Double click to pop the server');

           var node = createNode([0, -start-45*i, 0], [w-6, 16, d-30]);
           node.setHost(main);
           node.s({
               'front.image': 'server',
               'all.color': '#E6DEEC',
               'all.reverse.cull': true
           }); 
           node.pop = false;
           node.setToolTip('Double click to pop the server');
       }                    
   }
                
   main.p3(x, h/2+1, z);
}

這個服務器咱們還製做了門的打開以及關閉的動做,還有服務器內部的設備的彈出以及恢復位置。首先,對 3d 組件添加了過濾函數,對雙擊事件的元素過濾:工具

g3d.onDataDoubleClicked = function(data, e, dataInfo){//圖元被雙擊時回調
    if(data.face){//face是定義門時候開啓的屬性 是在節點定義的時候添加的 下面的 pop 也是同樣
        data.getHost().getAttaches().each(function(attach){//遍歷吸附到自身的全部節點的ht.List類型數組
            if(attach.pop){//pop 是定義設備是否彈出的屬性
                toggleData(attach);
            }
       });
    }
    toggleData(data, dataInfo.name);
};

 如下是對 toggleData 的定義:

function toggleData(data, name){//開關門 以及 設備彈出縮回的動做
    var angle = data.angle,
    pop = data.pop;
            
    if(angle != null){
        if(anim){
            anim.stop(true);//ht內置的函數 參數爲true時終止動畫
        }
        var oldAngle = data.window ? data.getRotationX() : data.getRotation();
        if(data.open){
            angle = -angle;
        }
        data.open = !data.open;
        anim = ht.Default.startAnim({
            action: function(v){
                if(data.window){
                    data.setRotationX(oldAngle + v * angle);    
               }else{
                   data.setRotation(oldAngle + v * angle);
               }                                
           }
       });
   }
   else if(pop != null){
        if(anim){
           anim.stop(true);
        }
        var p3 = data.p3(),
            s3 = data.s3(),
            dist = pop ? -s3[2] : s3[2];
        data.pop = !data.pop;                        
        if(data.pop){
            data.s({
                'note': 'Detail...',  
                'note.background': '#3498DB',
                'note.font': '26px Arial',
                'note.position': 6,
                'note.t3': [-30, -3, 30],
                'note.expanded': true,
                'note.toggleable': false,
                'note.autorotate': true                        
            });                                     
        }else{
             data.s('note', null);
        }                                                
        anim = ht.Default.startAnim({
             action: function(v){
                 data.p3(p3[0], p3[1], p3[2] + v * dist);                                                               
             }
        }); 
    }            
}

中間部分的服務器我就不贅述了,這裏聊一下比較有趣的部分,滅火器模型的製做,並非 Max3d 製做而成的,而是代碼生成的:

從圖中能夠看出來,這個滅火器是由底部一個圓柱,中間一個半圓形以及頂部一個小圓柱並在上面貼圖合成的:

function createFireExtinguisher(x, z, uvOffset){//建立滅火器
    var w = 80, 
          h = 200,
          body = createNode([0, 0, 0], [w, h, w]).s({//身體的圓柱
              'shape3d': 'cylinder',//圓柱
              'shape3d.image': 'fire',
              'shape3d.uv.offset': uvOffset,//決定3d圖形總體貼圖的uv偏移
              'shape3d.reverse.cull': true
          }),                    
          sphere = createNode([0, h/2, 0], [w, w, w]).s({
              'shape3d': 'sphere',//球體
              'shape3d.color': '#F20000',
              'shape3d.reverse.cull': true
          }),
          top = createNode([0, h/2+w/3, 0], [w/2, w/2, w/2]).s({//頭部的圓柱
              'shape3d': 'cylinder',
              'shape3d.color': '#242424',
              'shape3d.reverse.cull': true
          });
                    
    sphere.setHost(body);//設置吸附 只要body移動sphere也會移動
    top.setHost(sphere);
    body.p3([x, h/2, z]);
    body.setToolTip('Fire Extinguisher');
    sphere.setToolTip('Fire Extinguisher');
    top.setToolTip('Fire Extinguisher');
} 

 以上!這個 3d 機房的例子很是有表明性,性能也展現得很全面,以爲有必要拿出來說一下,但願能對大家有必定的幫助~

相關文章
相關標籤/搜索