基於 HTML5 WebGL 的 3D 網絡拓撲圖

  在數據量很大的2D 場景下,要找到具體的模型比較困難,而且只能顯示出模型的的某一部分,顯示也不夠直觀,這種時候能快速搭建出 3D 場景就有很大需求了。可是搭建 3D 應用場景又依賴於經過 3ds Max 或 Maya 的專業 3D 設計師來建模,Unity 3D 引擎作圖形渲染等,這對用戶來講都是挑戰!不過,HT 一站式的提供了從建模到渲染,包括和 2D 組件呈現和數據融合的一站式解決方案。HT 基於 WebGL 的 3D 技術的圖形組件 ht.graph3dView 組件經過對 WebGL 底層技術的封裝,與 HT 其餘組件同樣, 基於 HT 統一的 DataModel 數據模型來驅動圖形顯示,極大下降了 3D 圖形技術開發的門檻,在熟悉HT 數據模型基礎上,通常程序員只須要 1 小時的學習便可上手 3D 圖形開發。html

  好了,廢話很少說,先附上 Demohttp://www.hightopo.com/demo/blog_3dedge_20170630/index.htmlnode

  固然,這裏的我只是用簡單的圖形來表示設備,腦洞大開的你固然能夠將其換成更有意思的模型。程序員

  接下來看看咱們是怎麼作到的:服務器

一、準備工做:dom

3D 和 2D 的 API 的設計上保持了不少一致性,3D 視圖組件是 ht.graph3d.Graph3dView,  2D 視圖組件是 ht.graph.GraphView,二者可共享同一數據模型 DataModel。在 HT 中,爲了讓了得到接近真實三維物體的視覺效果,咱們經過透視投影使得遠的對象變小,近的對象變大,平行線會出現先交等更接近人眼觀察的視覺效果:ide

  如上圖所示,透視投影最終顯示到屏幕上的內容只有截頭椎體部分的內容,所以 GraphView 提供了 eye,center,up,far,near, fovy 和 aspect 參數來控制截頭椎體的具體範圍,咱們在實際運用中用到更多的是 eye 和 center:函數

    getEye() | setEye([x, y, z]),決定眼睛(或 Camera)所在位置,默認值爲 [0, 300, 1000];佈局

    getCenter() | setCenter([x, y, z]),決定目標中心點(或 Target)所在位置,默認值爲 [0, 0, 0];學習

詳情看 HT for Web 3D 手冊 手冊 (http://www.hightopo.com/guide/guide/core/3d/ht-3d-guide.html)。ui

dataModel = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dataModel);    
g3d.setEye(1800, 800, 1000);
g3d.setCenter(0, 100, 0);
g3d.setDashDisabled(false);
g3d.getView().style.background = 'rgb(10, 20, 36)';
g3d.addToDOM();

二、建立設備:

服務器,Demo 中的服務器實際上是經過 addStyleIcon 方式在服務器的位置添加圖片,詳情可看 HT for Web 入門手冊http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html):

//註冊圖片
ht.Default.setImage('server', 'server.png');

var server = new ht.Node();
server.s3(0, 0, 0);
server.p3(0, 60, 0);
server.addStyleIcon('icon', {
  position: 0,
  width: 200, 
  autorotate: true,
  transparent: true,               
  height: 200,
  names: ['server']
});
dataModel.add(server);

  工做臺,這裏的工做臺其實是立體圓柱來表示的,HT 在 GraphView 的 2D 圖形上,呈現各類圖形是經過 style 的shape 屬性決定,相似的 HT 在 3D 上提供了 shape3d屬性,預約義了多種 3D 的形體,詳情見HT for Web 3D 手冊。不過在這裏我並無用預約義的圖形,而是經過 ht.Default.createRingModel 的方式建立圓柱,該方法能夠根據 xy 平面的曲線,環繞一週造成 3D 模型,因此能夠用來定義多種圓形 3D 模型。

var desktop = new ht.Node();
desktop.s({
  '3d.selectable': false,
  'shape3d': ht.Default.createRingModel([
          0, 40,
          450, 40,
          450, 0,
          0, 40
  ], null, 20, false, false, 50),
  'shape3d.color': '#003333'
});
desktop.s3(1, 1, 1);
dataModel.add(desktop);

  平臺上的設備,咱們一共建立了 32 個設備:

var count = 32,
  radius = 400,
  index = count/2;
for (var i =  1; i <= count/2; i++) {
  var device1_angle1 = Math.PI * 2 * (index - i) / count,
    device1_angle2 = Math.PI * 2 * (index + i) / count,
    device1_angle3 = Math.PI * 2 * index / count;

  var device1_1 = createDevice(device1_angle1, radius, 60),
    device1_2 = createDevice(device1_angle2, radius, 60),
    device1_3 = createDevice(device1_angle3, radius, 60);

  layoutDevice1(device1_1, device1_angle1);
  var device1_edge1 = createEdge(device1_1, server, 'line1');
  device1_edge1.s({'shape3d.color': 'rgb(205, 211, 34)'});
  dataModel.add(device1_1);
  dataModel.add(device1_edge1);

  layoutDevice1(device1_2, device1_angle2);
  var device1_edge2 = createEdge(device1_2, server, 'line1');
  device1_edge2.s({'shape3d.color': 'rgb(205, 211, 34)'});
  dataModel.add(device1_2);
  dataModel.add(device1_edge2);

  layoutDevice1(device1_3, device1_angle3);
  var device1_edge3 = createEdge(device1_3, server, 'line1');
  device1_edge3.s({'shape3d.color': 'rgb(205, 211, 34)'});
  dataModel.add(device1_3);
  dataModel.add(device1_edge3);
}

  爲了讓建立的設備在平臺上的佈局更加合理,根據 index 計算出設備擺放角度,而且根據圓柱中心,圓盤半徑和角度計算出每一個設備擺放的位置:

function createDevice (angle, x, y) {
  var node = new ht.Node();
  cos = Math.cos(angle);
  sin = Math.sin(angle);
  node.p3(x*sin, y, x*cos);
  return node;
}

  其餘設備,

var num = 18;
var h = [800, 900, 1000, 1100, 1200];
var v = [40, 60, 80, 100];
var colors = ['#fcfc63', '#00E1E4'];
for (var j = 0; j < num; j++) {
  var device2_angle = Math.PI * j / num;
  var device2 = createDevice(device2_angle, h[Math.floor(Math.random()*5)], v[Math.floor(Math.random()*4)]);
  device2.s3(100, 20, 100); 
  device2.s({
    'shape3d': 'cylinder',
    'shape3d.color': colors[Math.floor(Math.random()*2)]
   });
  var device2_edge = createEdge(device2, desktop , 'line2');
  device2_edge.s({'shape3d.color': 'rgb(0, 203, 94)'});

  dataModel.add(device2);
  dataModel.add(device2_edge);         
}

三、連線

HT for Web 提供了默認的直線和多點的連線類型能知足大部分基本拓撲圖形應用,但在這裏咱們須要根據實際需求繪製曲線,因此,須要用到自定義連線類型,詳情看HT for Web 連線類型手冊

用 ht.Default.setEdgeType(type, func, mutual) 函數可用於自定義新連線類型:

    type:字符串類型的連線類型,對應 style 的 edge.type 屬性;

    func:函數類型,根據傳入參數(edge,gap,graphView,sameSourceWithFirstEdge)返回連線走向信息

               edge:當前連線對象;

               gap:多條連線成捆時,本連線對象對應中心連線的間距;

               graphView:當前對應拓撲組件對象;

               sameSourceWithFirstEdge:boolean 類型,改連線是否與同組的第一條同源;

               返回值爲 {points: new ht.List(...),segments:new ht.List(...)} 結構的連線走向信息,segments 可取值以下:

                      一、moveTo,佔用 1 個點信息;

                       二、lineTo,佔用 1 個點信息;

                       三、quadraticCurveTo,佔用 2 個點信息;

                       四、bezierCurveTo,佔用 3 個點信息;

                       五、closePath,不佔用點信息;

    mutual:該參數決定連線是否影響起始或結束節點上的全部連線,默認爲 false 表明隻影響同 source 和 target 的 EdgeGroup 中的連線,HT 預約義的連線類型中,後綴爲 2 的類型都是 mutural 爲 true 的複雜連線類型。

在 Demo 中定義了兩種類型的連線,分別爲 line1 和 line:

ht.Default.setEdgeType('line1', function(edge){
  var sourcePoint1 = edge.getSourceAgent().getPosition(),
    targetPoint1 = edge.getTargetAgent().getPosition(),
    points1 = new ht.List();       
  points1.add(sourcePoint1);
  points1.add({
    x: (sourcePoint1.x + targetPoint1.x)/2 + 200,
    e: sourcePoint1.e,
    y: (sourcePoint1.y + targetPoint1.y)/2
  });
  points1.add(targetPoint1);                          
  return {
    points: points1,
    segments: new ht.List([1, 3])
  };
});
ht.Default.setEdgeType('line2', function(edge){
  var sourcePoint = edge.getSourceAgent().getPosition(),
    targetPoint = edge.getTargetAgent().getPosition(),
    points = new ht.List();       
  points.add(sourcePoint);
  points.add({
    x: (sourcePoint.x + targetPoint.x)/2,
    e: ((sourcePoint.e + targetPoint.e)/2 || 0) - 300,
    y: (sourcePoint.y + targetPoint.y)/2
  });
  points.add({
    x: targetPoint.x,
    e: targetPoint.e -80, 
    y: targetPoint.y                            
  });
  return {
    points: points,
    segments: new ht.List([1, 3])
  };
});

  連線類型定義好,接下來就是建立連線,可是連線上還有流動效果,這個又怎麼實現呢?咱們 HT 有擴展流動線插件,能夠在 ht.Shape 和 ht.Edge 上增長流動效果,支持內部流動元素或用戶自定義的流動元素沿着路徑步進,要使用也很是方便,只須要引入 ht-flow.js 文件,詳情可見 HT for Web流動線手冊http://www.hightopo.com/guide/guide/plugin/flow/ht-flow-guide.html),可是插件並不適用於 3D 模型中,那在 3D 模型中該怎麼辦呢?即便不能使用現成的插件,咱們也能夠實現流動效果,能夠看HT for Web 入門手冊 中連線部分,咱們能夠將連線樣式經過 edge.dash 設置爲虛線後,動態改變 edge.dash.offset 虛線偏移,便可實現流動效果,因此,咱們建立連線時:

function createEdge (source, target , type) {
  var edge = new ht.Edge(source, target);
  edge.s({
    'edge.color': 'yellow',
    'edge.dash': true,
    'edge.dash.3d': true,
    'edge.dash.width': 4,
    'edge.type': type,                    
    'edge.dash.color': 'rgb(10, 20, 36)',                    
    'edge.dash.pattern': [20, 25]
  });
  edge.a({
    'flow.enabled': true,
    'flow.direction': -1,
    'flow.step': 4
  });
  return edge;
}

  最後,要讓虛線流動起來,可使用 HT 中的調度,詳情可看HT for Web 調度手冊http://www.hightopo.com/guide/guide/core/schedule/ht-schedule-  guide.html):

flowTask = {
  interval: 50,
  action: function(data){
    if(data.a('flow.enabled')){
      var offset = data.s('edge.dash.offset') + data.a('flow.step') * data.a('flow.direction');
      data.s('edge.dash.offset', offset);                        
    }
  }
};
dataModel.addScheduleTask(flowTask);

  到這裏,Demo 中的主要技術點都已經介紹了一遍,能夠看出咱們 HT 的強大之處,固然咱們官網上還有不少頗有意思的效果,你們也能夠看一看,也能夠玩一玩咱們的 HT 感覺它的強大之處,再次附上 Demo 地址: http://www.hightopo.com/demo/blog_3dedge_20170630/index.html

相關文章
相關標籤/搜索