基於 WebGL HTML5 的 3D 模型分離控制

圖撲軟體發表於2019-04-30

工業製作圖表,製作模型方面運用到 3d 模型是非常多的,在一個大的環境中,構建無數個相同的或者不同的模型,構建起來對於程式設計師來說也是一件相當頭疼的事情,我們使用 HT 幫大家解決了很大的難題,無數個例子可在官網上查詢到 http://hightopo.com/demos/index.html

本文 Demo 地址: http://hightopo.com/guide/guide/plugin/obj/examples/example_objajax.html

這次我們的例子是將這些例項中的一小部分思想抽取出來,作為給大家的分析,看看這次實現的例子效果圖:
在這裡插入圖片描述
這個例子用了 HT 中的樹元件 ht.widget.TreeView 和 HT 中載入 OBJ 格式檔案的 ht.Default.loadObj 函式來載入圖中的兩輛摩托車,我們利用程式碼來從頭開始解析這個例子的部分。

首先觀察這個例子的介面展示,會發現這個介面是由左右兩部分組成的,右邊又是由上下兩部分組成的,這種分割模式在 HT 中有很好的解決辦法,可以完全利用 ht.widget.SplitView 將介面分層,也可以利用 ht.widget.BorderPane 將介面分成上面、中間和下面。這裡我們因為是示例,所以將兩種方法都用上了:

dataModel = new ht.DataModel(); // 資料模型                
g3d = new ht.graph3d.Graph3dView(dataModel); // 3d 元件
toolbar = new ht.widget.Toolbar(item); // 工具條
borderPane = new ht.widget.BorderPane(); // 皮膚元件
borderPane.setTopView(toolbar); // toolbar 放在上部
borderPane.setCenterView(g3d); // g3d 放在中間部分
treeView = new ht.widget.TreeView(dataModel); // 樹元件
mainSplit = new ht.widget.SplitView(treeView, borderPane, 'h', 0.2); // 分割元件

其中 toolbar 中的 item 也是遵守 HT 設定規則的,item 是一個陣列,陣列中每一個元素都是 toolbar上的一部分,這個例子中 toolbar 只有2個元素,也具有足夠的代表性:

item = [
    {
        label: 'Editable',
        type: 'check',
        action: function(){
            g3d.setEditable(this.selected);
        }
    },
    {
        id: 'size',
        label: 'Size',
        slider: {
            width: 120,
            min: 1,
            max: 60,
            value: 1,
            thickness: 1,
            onValueChanged: function(){
            if(rawS3){
                var value = this.getValue();
                dataModel.each(function(data){
                if(data instanceof ht.Node){
                    data.s3(rawS3[0] * value, rawS3[1] * value, rawS3[2] * value); 
                    data.s({
                        'note.scale': value/20,
                        'note.t3': [0, -value, value]
                    });                                           
                }                                        
            });                                    
        }
    }
];

我們可以通過設定 toolbar 中的 item 元素來設定物件格式型別,其中 type 可以設定為 check、toggle 和 radio,分別表示核取方塊、開關按鈕和單選按鈕,這裡我們將是否可編輯 Editable 設定為核取方塊,可以通過控制這個元素來設定是否可編輯,後面的拉條也是 HT 中封裝的 ht.Slider 滑動條來繫結 OBJ 物件的 size 大小,通過控制滑動條來控制 OBJ 的大小,詳情請參考 HT for Web 工具條元件手冊

接著要將最外層的元件新增進底層 div 中,沒有閱讀過我的文章的同學這邊我解釋一下,所有的 HT 元件最根層都是一個 div 元件,可通過元件的 getView 函式獲得,預設和自定義互動時間監聽一般新增在該 div 上(getView().addEventListener(type, func, false)),渲染層一般由 canvas 提供,使用者可直接對根 div 和 canvas 層設定 css 樣式,也可以新增新的 HTML 元件到根 div 上,作為 canvas 的兄弟元件一起呈現。詳情請參考 HT for Web 入門手冊

接著通過利用 ht.widget.loadObj 函式將 OBJ 格式檔案匯入模型:

ht.Default.loadObj('obj/scooter.obj', 'obj/scooter.mtl', { // 左邊不可分割的摩托 
    cube: true,
    center: true,
    shape3d: 'scooter',
    finishFunc: function(modelMap, array, rawS3){
        window.rawS3 = rawS3;
        if(modelMap){
            var node = new ht.Node();
            node.setName('All in ONE');
            node.s({
                 'shape3d': 'scooter',
                 'wf.visible': 'selected',
                 'note': 'One Node',
                 'note.face': 'center',
                 'note.position': 7,
                 'note.background': 'blue',
                 'note.autorotate': 'y' 
            });
            node.s3(rawS3);
            node.p3(-300, 0, 0);
            dataModel.add(node);
        }
        checkLoaded();
    }
});

ht.Default.loadObj 函式有三個引數:

  1. objUrl,OBJ 檔案路徑;
  2. mtlUrl,MTL 檔案路徑;
  3. params JSON 結構引數。

parmas 引數可以設定 ht.Default.parseObj(text, mtlMap, params) 第三個引數的控制資訊,也就是說 ht.Default.parseObj 函式中的第三個引數所帶的控制資訊在 ht.Default.loadObj 函式中的第三個引數 params 中都可以使用,並且新增了 sync 和 finishFunc 引數,finishFunc 引數是用於載入後的回撥處理的函式,帶有引數 modelMap、array 和 rawS3 ,可以在 finishFunc 中做任何事情!詳情請參考 HT for Web OBJ 手冊

本例中有兩個 motor 摩托車模型,一個是整體的模型,不能拆分,一個是可以拆分成部分的模型,接下來就來看看如何將 OBJ 檔案中的模型拆分開來:

ht.Default.loadObj('obj/scooter.obj', 'obj/scooter.mtl', { // 右邊可分割的摩托             
    cube: true,
    center: true,
    finishFunc: function(modelMap, array, rawS3){
        if(modelMap){
            var lastNode = null,
            firstNode = null,
            parentNode = new ht.Data();    
                            
            parentNode.setName('Separate Scooter');
            dataModel.add(parentNode);                                                

            for(var name in modelMap){             
                var model = modelMap[name];   
                var shape3d = 'scooter:' + name;
                ht.Default.setShape3dModel(shape3d, model);                

                var node = new ht.Node();
                node.setName(name);
                node.setParent(parentNode);
                node.s({
                    'shape3d': shape3d,
                    'wf.visible': 'selected'
                });
                node.setHost(lastNode);
                lastNode = node;
                if(!firstNode){
                    firstNode = node;
                }
                node.s3(rawS3); 
                dataModel.add(node);
            }
            if(lastNode){
                firstNode.setHost(lastNode);
                firstNode.p3(300, 0, 0);
                firstNode.s({
                    'note': 'A lot of Nodes host together',
                    'note.face': 'center',
                    'note.position': 7,
                    'note.background': 'blue'
                });                                
            }                             
        } 
        checkLoaded();
    }
});  

我們可以通過 modelMap 獲得通過 ht.Default.parseObj 函式解析後的返回值,這個值通常就是 obj 格式的檔案解析後返回的 map 結構的 json 物件,每一個材質都對應一個模型資訊,詳情請參考 HT for Web OBJ 手冊

我們利用 ht.Default.parseObj 函式解析獲得每個材質的模型資訊,通過遍歷整個模型,獲得單獨的模型資訊,並且將其命名,這樣我們就能顯示每個模型的名稱了,也能對每個部分的模型進行控制。

上面程式碼中 45 行出現的 checkLoaded 函式是方便控制樹元件的展開合併的函式,我們在工業等各個領域中,用到“樹”的概念是非常多的,所以這個例子也運用到用作解釋:

function checkLoaded(){
    loadTask--;
    if(loadTask === 0){
        treeView.expandAll();
        treeView.selectAll();

        ht.Default.startAnim({
            action: function(t){
                toolbar.v('size', 50*t);
            }
        });                     
    }
}

因為“樹”的部分我們只用了兩個主節點,All in one 和 Seperate Scooter,我們定義 loadTask 變數值為 2,上面程式碼的意思是如果兩個模型都載入完畢,那麼就將樹元件 treeView 展開,並且全部選中,再用程式碼控制 toolbar 中的值為 50*t 來調整模型的大小。

對於樹元件 treeView,HT 封裝了很多幫助元件,使得開發速度更快,比如在這個例子中用到的 setSortFunc 函式,是用來設定排序的;setCheckMode 函式是用來設定 check 模式的,大家一看到 check 就會想到多選框,在這裡 HT 確實是通過設定 setCheckMode 函式來設定多選框,這個函式的引數可為:

  • null:預設值,不啟用check選擇模式
  • default:check模式的預設選擇方式,即單擊選中或取消選中,隻影響當前點選中的data物件
  • children:該check模式將同時影響點選中的data物件,以及其孩子物件
  • descendant:該check模式將同時影響點選中的data物件,以及其所有子孫物件
  • all:該check模式將同時影響點選中的data物件,以及其所有父輩和子孫物件

如果還是沒有理解,大家可以試試將這個函式註釋掉,你就能很清楚地明白它是做什麼用的了。或者檢視 HT for Web 樹元件手冊

以上就是今天的例子,實際開發中的例子會更復雜,邏輯也會更多,但通過 HT 來做,什麼事情都變得 so easy!

相關文章