WebGL 的 3D 模型交併補

建設性的立體幾何具備許多實際用途,它用於須要簡單幾何對象的狀況下,或者數學精度很重要的地方,幾乎全部的工程 CAD 軟件包都使用 CSG(能夠用於表示刀具切削,以及零件必須配合在一塊兒的特徵)。CSG 是 Constructive Solid Geometry 建模技術的簡稱,經過裁剪 subtract、融合 union 和相交 intersect 的運算,組合出複雜模型效果,HT 封裝了 ht.CSGNode 和 ht.CSGShape 等圖元類型來支持 CSG 的組合功能,經常使用於牆面的門窗挖空鑿洞的應用場景。css


CSG 對象能夠用二叉樹表示,其中葉子表示基元,節點表示操做。在這個圖中,節點被標記 ∩ 爲交集,∪ 爲並集,- 爲差集。CSG 提供的模型或表面看起來很複雜,但實際上不過是巧妙組合或分解對象。html

ht.CSGNode 繼承於 ht.Node,當 style 的 shape3d 屬性爲空時顯示爲六面體效果,CSGNode 若是經過 setHost 吸附到 宿主 CSGNode 或 CSGShape 後,宿主 CSGNode 或 CSGShape 可與吸附的 CSGNode 圖元進行 CSG 的組合建模。詳情請參考 HT for Web 建模手冊 CSGNode 章節。這裏我用 CSG 的概念寫了一個例子,讓你們能更好地理解這個概念。bash

本例 Demo 地址: hightopo.com/guide/guide…app

先來看下效果圖:ide


從上面效果圖能夠看到,咱們將界面分爲三個部分,這三個部分先是右邊部分上下分割,而後將整個界面左右分割,HT 用封裝好的 ht.widget.SplitView 進行界面的分割,而後將分割組件添加進底層 div 中:函數

dm = new ht.DataModel();// 數據模型            
treeView = new ht.widget.TreeView(dm); //樹組件                                                                                                 
gv1 = new ht.graph3d.Graph3dView(dm); //3D 組件  
gv2 = new ht.graph3d.Graph3dView(dm);
splitView = new ht.widget.SplitView(gv1, gv2, 'v', 0.6);//分割組件 
mainSplit = new ht.widget.SplitView(treeView, splitView, 'h', 0.27);   
                
view = mainSplit.getView();  
view.className = 'main';
document.body.appendChild(view);    
window.addEventListener('resize', function (e) {
    mainSplit.invalidate();
}, false);     複製代碼

上面代碼是一種很是常見的在 HTML 中添加 HT 組件的方法,詳情可參考 HT for Web 入門手冊組件章節。這種方法進行添加 HT 組件有一個須要注意的點,由於 HT 通常都以設置 position 爲 absolute 的絕對定位方式,必須設置 left、right、top、bottom 等等基礎 css 樣式,像這樣:ui

.main {
     margin: 0px;
     padding: 0px;
     position: absolute;
     top: 0px;
     bottom: 0px;
     left: 0px;
     right: 0px;
}複製代碼

因此爲了最外層組件加載填充滿窗口的方便性,HT 的全部組件都有 addToDOM 函數,其思想邏輯以下,其中 iv 是 invalidate 的縮寫:this

addToDOM = function(){   
    var self = this,
    view = self.getView(),   
    style = view.style;
    document.body.appendChild(view);            
    style.left = '0';
    style.right = '0';
    style.top = '0';
    style.bottom = '0';      
    window.addEventListener('resize', function () { self.iv(); }, false);            
}複製代碼

之後咱們在代碼中就能夠直接調用 addToDOM 函數,而不用寫一大堆代碼了,上面代碼用 addToDOM 取代以後的代碼以下,並且不用描繪 css 樣式:spa

dm = new ht.DataModel();// 數據模型             
treeView = new ht.widget.TreeView(dm); //樹組件                                                             
gv1 = new ht.graph3d.Graph3dView(dm); //3D 組件  
gv2 = new ht.graph3d.Graph3dView(dm);
splitView = new ht.widget.SplitView(gv1, gv2, 'v', 0.6);//分割組件 
mainSplit = new ht.widget.SplitView(treeView, splitView, 'h', 0.27);   
mainSplit.addToDOM();複製代碼

界面分配好以後咱們就要對其添加內容了,界面的左邊部分是 HT 封裝的樹組件,我在以前的文章寫到過,樹組件是一個很是方便的繪製樹形關係的組件,開發人員可以輕鬆地從數據模型 DataModel 中獲取數據和節點之間的關係放到樹上,只須要在樹組件聲明的過程當中,將對應的數據模型 DataModel 放進樹組件的參數便可,固然咱們還擴展了不少跟樹組件有關的函數,很是方便實用,這裏咱們只用了 expandAll 函數,將全部對象展開:3d

treeView = new ht.widget.TreeView(dm); //樹組件   
treeView.expandAll();複製代碼

右邊部分上下分爲兩部分,都是 3D 場景,就是設置顯示有點不一樣,其餘徹底相同,上面的 3D 場景重載了 getVisibleFunc 函數,若是元素的 showMe 屬性爲 true,則可視;若是節點爲 ht.CSGNode 類型而且節點的 getHost 函數的參數爲空,則不可視;其餘狀況都可視:

gv1.setVisibleFunc(function(data){
    if(data.showMe){
        return true;
    }
    if(data instanceof ht.CSGNode && data.getHost()){
        return false;
    }
    return true;
});複製代碼

咱們先向 3D 場景中添加元素對象,咱們先解釋中間的書架,對兩邊的書架有缺的再進行補充。首先咱們添加了一個 ht.CSGNode 節點 shelf,做爲書架的主節點,其餘的節點都是依附於這個節點的,對這個節點設置了位置、大小、名稱以及六個面的顏色,而後添加進數據模型 DataModel:

var shelf = new ht.CSGNode();
shelf.s3(500, 400, 120);
shelf.p3(0, 200, 0);
shelf.setName('shelf1');
shelf.s({
    'all.color': '#E5BB77'
});
dm.add(shelf);複製代碼

接着向這個 shelf 中添加 10 個節點,作書架的格子效果,並設置依附關係和父子關係添加進數據模型中:

for(var i=0; i<2; i++){
    for(var j=0; j<5; j++){
        var clipNode = new ht.CSGNode();
        clipNode.setHost(shelf);
        clipNode.s3(80, 100, 120);
        clipNode.p3(-200+j*100, 340-i*120, 20);
        clipNode.setName('substract-'+i+'-'+j);
        clipNode.s('batch', 'tt');
        clipNode.setParent(shelf);
        dm.add(clipNode);
    }
}複製代碼

爲了讓書架變得更美觀一點,咱們在書架的上下左右都加上了 ht.CSGNode,最後爲了更加具象化,咱們還添加了一本書,實現方式也差很少,都很是簡單:

var book = new ht.Node();
book.setName('CSS3: The Missing Manual');
book.s3(60, 80, 8);
book.p3(-100, 210, 20);
book.r3(-Math.PI/6, Math.PI/5, 0);
book.setIcon('book');
book.s({
    'front.image': 'book',
    'back.color': 'white',
    'left.color': 'white',
    'all.color': 'gray'
});
book.setHost(shelf);
book.setParent(shelf);
dm.add(book); 複製代碼

接着左邊的書架也是相似的構建方法,有一點不一樣的是,這邊有一個 ht.CSGBox 類型,繼承於 ht.CSGNode,其除具有父類 CSGNode 的挖空等功能外,還可對六個面進行旋轉展開關閉的操做,這裏咱們的節點只設置了前面的可以旋轉展開,而且設置了一系列的樣式:

clipNode = new ht.CSGBox();
clipNode.setName('CSGBox-Expand-Left');
clipNode.s3(100, 100, 120);
clipNode.p3(0, 65, 0.1);
clipNode.setHost(shelf);
clipNode.showMe = true;
clipNode.s({
    'all.visible': false,//6面均不可見
    'front.visible': true,//前面可見
    'front.toggleable': true,//容許前面雙擊展開                    
    'front.reverse.flip': true,//前面的反面顯示正面的內容
    'front.transparent': true,//前面透明
    'front.end': Math.PI * 0.7,//前面展開狀態的結束旋轉弧度
    'front.color': 'rgba(0, 50, 50, 0.7)'//前面顏色
});複製代碼

可能大家還想知道下面的地球是怎麼作到的?還記得以前的文章寫到過 HT 中設置了 shape3d 屬性,設置這個屬性實際上就是在操做 setShape3dModel(name, model) 和 getShape3dModel(name),能夠經過這個屬性設置爲 box|sphere|cylinder|cone|torus|star|rect|roundRect|triangle|rightTriangle|parallelogram|trapezoid 等等模型,這些模型也都是 HT 封裝好的,要使用時直接設置 shape3d 爲其中的一個值便可,如這個例子中用到 「shape3d: sphere」 就是設置爲球體。咱們簡單地用一張地圖圖片包裹在這個球體的外側,固然,這張地圖圖片是先經過 ht.Default.setImage 註冊過的,而後經過 shape3d.image 將圖片附到這個節點上

earth = new ht.Node();
earth.setName('earth');
earth.s3(70, 70, 70);
earth.p3(0, 50, 0);
earth.s({
    'shape3d': 'sphere',
    'shape3d.image': 'earth'
});
earth.setHost(shelf);  
earth.setParent(shelf);
dm.add(earth);複製代碼

右邊的書架,一樣也是有一個主節點,其餘節點依附於它,可是咱們看到這邊換了一個新的節點類型 ht.DoorWindow,ht.DoorWindow繼承於 ht.CSGNode,其除具有父類 CSGNode 的挖空等功能外,還可進行總體的旋轉展開關閉的操做, 經常使用於做爲門或窗的業務對象,吸附於 CSGNode 或 CSGShape 的 host 做爲牆面的圖元。這個節點類型就是 ht.CSGNode 的延展,相對來講就是區分了實際應用而添加了不一樣的 style 參數,更多的屬性請到 HT for Web 建模手冊 DoorWindow 章節 查看而後添加到節點中玩玩:

photos = new ht.DoorWindow();
photos.setName('DoorWindow-Photos');
photos.setIcon('ben12');
photos.s3(110, 100, 130);
photos.p3(5, 180, 0);                
photos.setHost(shelf);  
photos.showMe = true;
photos.s({                    
    'bottom.uv': [1,1, 1,0, 0,0, 0,1],
    'bottom.uv.scale': [1, 1],
    'left.uv.scale': [3, 3],
    'top.uv.scale': [2, 2],
    'dw.s3': [0.8, 0.9, 0.05],
    'dw.t3': [0, -5, 0],
    'dw.axis': 'v',
    'dw.toggleable': false,
    'front.image': 'ben1',
    'back.image': 'ben2',
    'all.color': '#F8CE8B'
});
photos.setParent(shelf);
dm.add(photos);複製代碼

最後,咱們將左側的地球 earth 和右側的照片 photo 旋轉起來:

var angle = 0;
setInterval(function(){
    angle += Math.PI/40;
    earth.r3(0, angle, 0);
    photos.s('dw.angle', angle);
}, 50);複製代碼

咱們看到,其實雖然 HT 封裝了不少不一樣的 CSG 節點類型,可是實際應用都差很少,並且內容也沒有差特別多,差異都是在 style 參數上,可是真的在實際開發中,這種區分就會很大程度上加快開發速度,畢竟名稱一目瞭然,就知道要運用哪些 style 屬性了。

相關文章
相關標籤/搜索