如今,3D 模型已經用於各類不一樣的領域。在醫療行業使用它們製做器官的精確模型;電影行業將它們用於活動的人物、物體以及現實電影;視頻遊戲產業將它們做爲計算機與視頻遊戲中的資源;在科學領域將它們做爲化合物的精確模型;建築業將它們用來展現提議的建築物或者風景表現;工程界將它們用於設計新設備、交通工具、結構以及其它應用領域;在最近幾十年,地球科學領域開始構建三維地質模型,並且 3D 模型常常作成動畫,例如,在故事片電影以及計算機與視頻遊戲中大量地應用三維模型。它們能夠在三維建模工具中使用或者單獨使用。爲了容易造成動畫,一般在模型中加入一些額外的數據,例如,一些人類或者動物的三維模型中有完整的骨骼系統,這樣運動時看起來會更加真實,而且能夠經過關節與骨骼控制運動。javascript
這些種種都讓咱們前端開發者以爲若是咱們能夠不用學習 unity3d 或者其餘遊戲開發工具就能實現 3D 效果,並且可以精準的靠代碼來控制移動或者方向就行了。因而我利用 HT For Web 中的 3D組件 來實現了一個小例子,用了 HT 中 3D組件 的大部分功能,作這個例子就是想把 3D 組件好好的掌握,儘可能放進一個例子中,到時候別人有須要就能夠參考了。html
先來看看總體實現的效果圖: 前端
用 HT for Web,現有的 3D 模板建立三層底板不是問題,問題是要如何將圖中第一層的「電腦」和「機櫃組件」放上去?我是在網上 down 下來的 obj 格式的文件,而後我利用 HT 中的 ht.Default.loadObj(objUrl, mtlUrl, params)
函數將模型加載進去,其中的 params 部分能夠參考 www.hightopo.com/guide/guide… 代碼以下:java
ht.Default.loadObj('obj/機櫃組件1.obj', 'obj/機櫃組件1.mtl', { // 加載 obj 文件
cube: true, // 是否將模型縮放到單位1的尺寸範圍內,默認爲false
center: true, // 模型是否居中,默認爲false,設置爲true則會移動模型位置使其內容居中
shape3d: 'box', // 若是指定了shape3d名稱,則HT將自動將加載解析後的全部材質模型構建成數組的方式,以該名稱進行註冊
finishFunc: function(modelMap, array, rawS3){ // 用於加載後的回調處理
if(modelMap){
device2 = createNode('box', floor1); // 建立一個節點,在第一層「地板」上
     device2.p3([x1-120, y1+13, z1+60]); // 設置這個節點座標
     device2.s3(rawS3); // 設置這個節點大小
     createEdge(device1, device2); // 建立連線
     device3 = createNode('box', floor1);
     device3.s3(rawS3);
     device3.p3([x1+120, y1+13, z1+60]);
     createEdge(device1, device3);
}
}
});
複製代碼
其中 finishiFunc 函數中的三個參數定義以下:算法
通常在實際應用中咱們都會將圖元的大小設置爲模型的原始尺寸。數組
「電腦」上方有個紅色的立體能旋轉的「警告」,是依靠 ht.Default.setShape3dModel 函數(HT for Web 建模手冊)註冊的一個 3D 模型,在 ht 中,封裝好的建模函數有不少,比較基礎的就是球體,圓柱,立方體等等,這邊我用的是構造環形的方法 createRingModel 來生成「警告」最外面的環,感嘆號的上部分就是用的 createSmoothSphereModel 構造的球體,感嘆號的下部分就是用 createSmoothCylinderModel 來構造的圓柱。我一開始直接使用了 3D 模型中封裝好的函數,致使後來根本不知道函數中使用的參數是作什麼用的,並且也不明白 3D 模型是怎麼構成的,而後本身又從新看了前面的「模型基礎」,才知道原來 3D 模型採用的一個面,最基礎的是三角面,以後複雜的面也是由多個三角面來造成的,而後繞着一根特定的軸旋轉以後造成的,固然,這個軸是你來決定的,不一樣的軸能夠生成不一樣的形狀,對於顏色等風格方面的設置能夠參考 HT for Web 風格手冊。至於如何讓這個 3D 模型旋轉起來,ht 中封裝了 addScheduleTask(Task) 方法,我在第三層 Task 中調用了 ht 封裝的一個旋轉函數 setRotation 來設置旋轉的順序和方向,而且指定了旋轉的對象。如下是自定義「警告」的 3D 模型的方法(注意:由於本例的模型是自定義組合的,若是要設置總體模型的顏色要用 「all.blend」 style 屬性):ide
function createAlarm(device, formPane) {
var ringModel = ht.Default.createRingModel([ 8, 1, 10, 1, 10, -1, 8, -1, 8, 1 ], null, null, false, false, 100); // 根據xy平面的曲線,環繞一週造成3D模型。
var sphereModel = ht.Default.createSmoothSphereModel(8, 8, 0, Math.PI*2, 0, Math.PI, 2); // 構建光滑球體模型
var cylinderModel = ht.Default.createSmoothCylinderModel(8, true, true, 1, 2, 0, Math.PI*2, 8); // 構建光滑圓柱體模型
var alarmArr = [ // 組合模型 由三個模型ringModel、sphereModel、cylinderModel組合而成
{
shape3d: ringModel, // 定義模型類型
r3: [Math.PI/2, 0, 0], // 設置旋轉角度
color: { // 設置模型顏色
func: 'style@all.blend', // 數據綁定style樣式中的all.blend屬性,可經過data.s()獲取和設置這個屬性
}
},{
shape3d: sphereModel,
t3: [0, 4, 0],
color: {
func: 'style@all.blend',
}
},{
shape3d: cylinderModel,
t3: [0, -3, 0],
color: {
func: 'style@all.blend',
}
}
];
ht.Default.setShape3dModel('alarm', { // 註冊自定義3D模型
shape3d: alarmArr
});
var alarmTip = createNode('alarm', device); // 建立shape3d爲alarm的節點
alarmTip.s3([2, 2, 2]); // 設置節點大小
alarmTip.p3(device.p3()[0], device.p3()[1]+60, device.p3()[2]);
alarmTip.s('all.blend', 'red'); // 改變此屬性可改變模型的顏色,由於模型建立的時候已經數據綁定了
return alarmTip;
}
複製代碼
接下來看看怎麼讓這個「告警」節點「閃爍」,我是直接將這個動畫跟節點綁定,這樣能夠直接經過節點來控制動畫。因此在上面咱們建立 alarm 的模型時就能夠直接將動畫綁在節點上:函數
if(formPane){
alarmNode.scaleFunc = function() { //設置大小變化動畫
var size = alarmNode.s3(); //獲取節點的大小
if (size[0] === 2 && size[1] === 2 && size[2] === 2) alarmNode.s3([1, 1, 1]);
else alarmNode.s3([2, 2, 2]);
alarmNode.scaleTimer = setTimeout(alarmNode.scaleFunc, formPane.v('scaleInterval')); //設置動畫
}
alarmNode.blinkFunc = function(){ //設置閃爍的動畫
var color = alarmNode.s('all.blend'); //獲取節點的style樣式
if (color === 'red') alarmNode.s({'all.blend': 'yellow'}); //若是節點顏色爲紅色,那麼設置爲黃色
else alarmNode.s({'all.blend': 'red'});
alarmNode.blinkTimer = setTimeout(alarmNode.blinkFunc, formPane.v('blinkInterval'));
}
alarmNode.rotateFunc = function() { //設置旋轉動畫
alarmNode.setRotation(alarmNode.getRotation() + Math.PI/20); //獲取節點當前的旋轉角度,在這個旋轉角度之上添加 Math.PI/20 個角度
alarmNode.rotateTimer = setTimeout(alarmNode.rotateFunc, formPane.v('rotInterval'));
}
}
複製代碼
上面的動畫我設置了能夠經過 form 表單面板上的屬性來控制節點閃爍的速度,以及閃爍節點的動畫等等,主要說一下這個功能在 form 表單上的實現:工具
formPane.addRow([ // 向form表單面板上添加一行元素
{
checkBox: { // 複選框
label: 'Enable Blink', // 複選框對應的文本內容
selected: true, // 設置選中複選框
onValueChanged: function(){ // 複選框值變化時回調的函數
var data = dataModel.getDataByTag('colorAlarm'); // 經過tag標籤獲取節點
if (this.getValue()) { // 獲取複選框當前值true/false
data.blinkTimer = setTimeout(data.blinkFunc, formPane.v('blinkInterval')); // 直接經過設置節點的blinkTimer來設置動畫
}
else {
clearTimeout(data.blinkTimer); // 清除動畫
}
}
}
},
{
id: 'blinkInterval', // form能夠經過getValue(簡寫爲v)來獲取這個item的值
slider: { // 設置了該屬性後HT將根據屬性值自動構建ht.widget.Slider對象,並保存在element屬性上
min: 0, // 滑動條最小值
max: 1000, // 滑動條最大值
step: 50, // 滑動條步進
value: 500, // 當前滑動條的值
}
}
], [0.1, 0.1]); // 設置這行的兩個item元素的寬度小於1的值爲比例
複製代碼
最後來講說 3D 管線上的小球流動的部分,這個功能確實很是實用,並且作出來的效果也確實不錯,跟你們分享~學習
首先,建立一條連線鏈接起始節點和結束節點並設置這個連線的樣式,用 ht.Edge 能夠將連線吸附在起始節點和結束節點上,這樣移動這兩個節點中的任意一個節點連線都會跟着節點移動的位置變化,很是方便:
var polyline = new ht.Edge(source, target); // 建立連線
dataModel.add(polyline); // 將連線添加進數據容器中
polyline.s({
'edge.width': 5, // 連線寬度
'edge.type': 'points', // 連線類型 爲points時連線走向將由edge.points屬性決定,用於繪製折線
'edge.points': [ // 可設置類型爲ht.List的{x:100, y:100}格式的點對象數組,當edge.type爲points時起做用
{x: source.getPosition3d()[0]+200, y: source.getPosition3d()[2], e: source.getPosition3d()[1]},
{x: target.getPosition3d()[0]+400, y: target.getPosition3d()[2], e: target.getPosition3d()[1]}
],
'edge.segments': [1, 4], // 用於描述點鏈接樣式,數組元素爲整型值
'shape3d': 'cylinder', // 圓柱
'shape3d.color': 'rgba(242, 200, 40, 0.4)',
'shape3d.resolution': 30, // 微分段數,能夠決定曲線的平滑度
'edge.source.t3': [20, 0, 0], // 連線source端偏移,[tx, ty, tz]格式,默認爲空
'edge.target.t3': [20, 0, 0] // 連線target端偏移,[tx, ty, tz]格式,默認爲空
});
複製代碼
由於咱們在建立連線的時候設置的 points 僅爲曲線上的兩個點,因此若是要獲取曲線目前造成的點,是缺乏 source 和 target 兩個點的,咱們從新設置一個數組,將這兩個點添加進去,後面獲取曲線上全部點時會用上:
var list = new ht.List();
list.push({x: source.getPosition3d()[0], y: source.getPosition3d()[2], e: source.getPosition3d()[1]}); // 向數組中添加source點
polyline.s('edge.points').each(function(item){ // 添加style屬性中已設置的兩個點
list.push(item);
});
list.push({x: target.getPosition3d()[0], y: target.getPosition3d()[2], e: target.getPosition3d()[1]}); // 添加target點
複製代碼
而後建立一個在管線上滑動的小球節點,這是僅是設置節點,真正添加進數據容器 dataModel 中須要設置完小球的座標時再添加,若是沒有給節點設置位置就將節點添加進數據容器中,節點的初始位置就是 3D 場景的正中心 [0, 0, 0] 的位置。小球滑動的動畫代碼以下:
var ball = new ht.Node(); // 建立小球節點
ball.s({ // 設置小球節點的樣式
'shape3d': 'sphere', // 設置小球的3d模型爲球形
'shape3d.color': 'rgba(40, 90, 240, 0.4)' // 設置3d模型的顏色
});
var delta = 10, flag = 0;
setInterval(function(){
flag++;
var length = (polyline.a('total') || 0) % polyline.a('length') + delta; // 小球當前走過的曲線長度
var cache = ht.Default.getLineCacheInfo(list, polyline.s('edge.segments')); // 獲取曲線上的點的信息
var lineLength = ht.Default.getLineLength(cache); // 獲取曲線的總長度
polyline.a('length', lineLength - 50); // 由於我設置了edge的t3(至關於2d中的offset),因此線段長度實際沒有那麼長
var offset = ht.Default.getLineOffset(cache, length); // 曲線根據曲線上點的信息的偏移量
ball.setPosition3d(offset.point.x + 10, offset.point.y, offset.point.z); // 設置節點的座標
polyline.a('total', length);
if(flag === 1) dataModel.add(ball); // 這時候節點已經有了座標了,能夠添加進數據容器中了
}, 10);
複製代碼
咱們還能夠看到第二層上有兩個特殊的多邊形「平行四邊形」和「梯形」,平行四邊形是靠 createParallelogramModel 模型函數,這個函數比較簡單,createExtrusionModel(array, segments, top, bottom, resolution, repeatUVLength, tall, elevation)
,array 是你要造成的圖形的座標點,這邊只是針對於 xz 軸上畫的平面圖形,segments 指的是如何鏈接這幾個座標點,可參考 HT for Web 形狀手冊,top 和 bottom 就是讓你選擇是否有頂部或者底部,resolution 微分段數,咱們描繪一段曲線的時候可能只要確認幾個個別的點而後在每兩個點之間的連線上把它分紅多個段,這樣這條線段就會變得平滑,ht 爲了用戶可以輕鬆操做這些線段,就封裝了這一個參數,repeatUVLength 默認爲空,設置值後頂部和底部的貼圖將根據制定長度值進行重複,tall 模型的高度,默認爲 5,elevation 模型中心的 y 軸位置,默認值爲 0,設置這個值可使 xz 上的平面繞着 y 軸旋轉。
底層的一個環形的效果是經過一個算法來實現的,環形得確認這個環形上有多少個元素,而後算每兩個之間的角度,在經過 sin、cos 來計算每個元素的位置,得出了以下代碼:
names = ['設備2', '設備3', '設備4', '設備5', '設備6', '設備7', '設備8', '設備9'];
names.forEach(function(name, index) {
x = 400, y = 200, angle = 45, r = 120;
x = x3 + Math.sin((2 * Math.PI / 360) * angle * index) * r;
y = z3 + Math.cos((2 * Math.PI / 360) * angle * index) * r;
device = createRect([x, y3 + 15, y], [w * 0.1, 15, h * 0.1], '', '', floor3);
createEdge(device5, device);
});
複製代碼
其餘若是還有不懂的部分能夠去官網(hightopo.com)查看對應的手冊,或者留言私信均可以。
最後附上本文例子:www.hightopo.com/demo/3DTopo…