基於 HTML5 WebGL 實現的 3D “彈力”佈局

圖撲軟體發表於2020-04-06

分子力(molecular force),又稱分子間作用力、範得瓦耳斯力,是指分子間的相互作用。當二分子相距較遠時,主要表現為吸引力,這種力主要來源於一個分子被另一個分子隨時間迅速變化的電偶極矩所極化而引起的相互作用;當二分子非常接近時,則排斥力成為主要的,這是由於各分子的外層電子雲開始重疊而產生的排斥作用。

HT for Web 提供了彈力佈局(也稱為力導向佈局)的功能,即根據節點之間存在互斥力,相互連線的節點間存在引力, 彈力佈局執行一段時間後,整體拓撲網路結構會逐漸達到收斂穩定的平衡狀態。這個功能很有趣,今天我們就將它的魅力展現出來。

本例地址:http://www.hightopo.com/demo/pipeline/index.html
圖片描述
使用彈力佈局功能需要在引入 ht.js 核心庫之後,再引入一個 ht-forcelayout.js 的彈力佈局外掛庫,因為還用到了 form 表單,所以要引入 ht-form.js 的表單外掛庫:

<script src="../../guide/lib/core/ht.js"></script>
<script src="../../guide/lib/plugin/ht-forcelayout.js"></script>
<script src="../../guide/lib/plugin/ht-form.js"></script>

ht.layout.Force3dLayout 類提供 3D 彈力佈局,建構函式可傳入 DataModel 和 Graph3dView 兩種引數。 預設僅對未選中圖元進行佈局,如果建構函式引數為 Graph3dView 時,則檢視元件的 isMovable 和 isVisible 函式將影響圖元是否可佈局, 圖元 style 上的 layoutable 屬性也可設為 false 阻止圖元參與佈局。

介紹完 HT 封裝的彈力佈局的背景之後,接下來就是幫助你們也能輕鬆地實現這個效果。

首先我們定義一個顏色陣列變數,儲存各個彈力球的顏色,還定義了一個隨機函式,用來生成數隨機的陣列中的顏色:

var colorList = ['#FFAFA4', '#B887C5', '#B9EA9C', '#CFD9E7', '#4590B8', '#FF9C30'], 
    colorLen = colorList.length;
var randomColor = function() {
    var ran = Math.random() * colorLen;
    return colorList[Math.floor(ran)]; // 隨機 6 種顏色
};

接著建立彈力球,簡單生成一個 3D 節點,通過設定這個節點的 style 樣式屬性來控制節點的顯示方式,其中將“shape3d”設定為“sphere”即可將 ht.Node 六面體變成 3D 球體模型,再設定“shape3d”屬性為前面定義的隨機顏色,s3 是 HT 封裝的設定 3D 節點大小的 setSize3d 函式的簡寫,最後將這個節點新增進資料模型 dataModel 中:

var createNode = function(dm) { // 建立 node 節點 圓
    var node = new ht.Node();
    node.s({ // 設定樣式為 setStyle 的簡寫
        'shape3d': 'sphere',
        'shape3d.color': randomColor() // 設定隨機顏色
    });
    node.s3(40, 40, 40);
    dm.add(node);
    return node;
};

現在效果圖上出現的還有各個彈力球之間的連線,這個連線我們一看就覺得很不一般,也是通過構造一個一個節點,這個節點是通過 HT for Web 建模手冊 setShape3dModel函式自定義的 ht.Default.createRingModel 根據 xy 平面的曲線,環繞一週形成的 3D 環形模型,將其命名為‘custom’:

ht.Default.setShape3dModel( // 建立模型 根據 xy 平面的曲線,環繞一週形成 3D 模型。
    'custom', ht.Default.createRingModel( [0.5, 0.5, -0.2, 0, 0.5, -0.5], [1, 3] )
);

HT 將使用者自定義的屬性和 HT 預設的屬性呼叫方法分為 node.a 和 node.s 這樣就能將兩者有效地區分開來(具體參照 HT for Web 入門手冊 style 章節),我們在建立管線的時候就用了這種方法:

var updatePipeline = function(edge) { // 重新設定 edge 的樣式
    var pipeline = edge.a('pipeline');
    pipeline.s3(1, 1, 1); // 設定大小
    pipeline.p3(0, 0, 0); // 設定座標

    var node1 = edge.getSourceAgent(), // 獲取圖形上連線的起始節點
    node2 = edge.getTargetAgent(); // 獲取圖形上連線的目標節點
    pipeline.s('mat', createMatrix(node1.p3(), node2.p3(), 20)); // 3d 整體圖形矩陣變化
};

最神祕的是如何能做出讓兩個節點“若即若離”的效果?

我們知道,矩陣能描述任意線性變換。線性變換保留了直線和平行線,線性變換保留直線的同時,其他的幾何性質如長度、角度、面積和體積可能被變換改變了。簡單的說,線性變換可能“拉伸”座標系,但不會“彎曲”或“卷折”座標系。這個函式主要是將我們的連線線在拖動彈力球后被拖拉的連線線的進行一個“變化矩陣”的操作,變化矩陣也是 HT 封裝的 ht.Default.createMatrix 函式,通過將節點的 style 屬性 mat 設定為一個自定義的函式,就是將這個節點的座標乘上在“mat”屬性對應的值,也就是說如果當前這個管線的旋轉角為 [Math.PI/6, 0, 0],假設我們在 createMatrix 函式中設定 r3 為 [Math.PI/3, 0, 0],那麼這個節點會旋轉 90 度。非常輕鬆地建立出變化矩陣:

var createMatrix = function(p1, p2, width) { // createMatrix(array, matrix) 將一組 JSON 描述的縮放、移動和旋轉等操作轉換成對應的變化矩陣
    var vec = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]],
        dist = ht.Default.getDistance(p1, p2); // 獲取兩點之間距離,或向量長度
    return ht.Default.createMatrix({
        s3: [width, dist, width],
    r3: [Math.PI/2 - Math.asin(vec[1]/dist), Math.atan2(vec[0], vec[2]), 0],
    rotationMode: 'xyz',
    t3: [(p1[0]+p2[0])/2, (p1[1]+p2[1])/2, (p1[2]+p2[2])/2]
    });
};

基礎配件全部定義完畢,接著就是將“shape3d”屬性設定為自定義的 3D 模型“custom” ,並將“layoutable”屬性設定為“false”阻止圖元參與佈局,並將點之間的連線通過edge.a(‘pipeline’, node)重新重新整理,並新增進資料模型 dataModel 中:

var createEdge = function(dm, node1, node2) { // 建立‘custom’模型的 edge
    var node = new ht.Node();
    node.s({
        'shape3d': 'custom',
        'shape3d.color': '#ECE0D4',
        'layoutable': false
    });
    dm.add(node);

    var edge = new ht.Edge(node1, node2);
    edge.a('pipeline', node);
    edge.s('edge.color', 'rgba(0, 0, 0, 0)');
    dm.add(edge);
    return edge;
};

插:我們還可以在工業上用 HeatMap 熱圖上做文章,效果依舊很炫,具體地址 http://hightopo.com/guide/guide/plugin/forcelayout/examples/example_heatmap3d.html
圖片描述

介面上的圖形全部繪製完畢,剩下的就只有 form 表單,首先將 form 表單新增進 HTML 頁面,用的是 HT 封裝的 ht.widget.FormPane 函式:

var formPane = new ht.widget.FormPane();
formPane.setWidth(230);
formPane.setHeight(125);
formPane.addToDOM();

記住,form 表單要設定寬高,不然不顯示。

form 表單新增行是通過 addRow 函式,我們重點來說一下下面的幾行,Color、Range 和 Intensity,這三個名字主要是用來控制“頭燈”的。在 HT 中直接通過 setHeadlightColor/setHeadlightRange/setHeadlightIntensity 三個函式來控制“頭燈”的顏色、範圍以及燈的強度,onValueChanged 屬性,顧名思義屬性值改變之後觸發的事件:

['Color', 'Range', 'Intensity'].forEach(function(name) {
    var obj = { id: name },
    func = function(oV, nV) {
        g3d['setHeadlight' + name](nV); // === g3d.setHeadlightColor(nV)/g3d.setHeadlightRange(nV)/g3d.setHeadlightIntensity(nV)
    };
    if (name === 'Color')
        obj.colorPicker = { // ht.widget.ColorPicker 為顏色選擇框 
        instant: true,
        value: g3d['getHeadlight' + name](), // === g3d.getHeadlightColor()
        onValueChanged: func
    };
    else 
        obj.slider = { // 滑動條
            min: 0,
        max: name === 'Range' ? 20000 : 3,
        step: 0.1,
        value: g3d['getHeadlight' + name](),
        onValueChanged: func
        };
    formPane.addRow([ name, obj ], [ 70, 0.1 ]);
});

slider 和 colorPicker 都是 HT 自定義的滑動條和顏色選擇器,詳情請參考 HT for Web 表單手冊

如果還有不懂的請諮詢我,或者可以直接上 HT for Web 官網查閱手冊。

相關文章