HT圖形元件設計之道(四)

圖撲軟體發表於2014-10-08

在《HT圖形元件設計之道(二)》我們展示了HT在2D圖形向量的資料繫結功能,這種機制不僅可用於2D圖形,HT的通用元件甚至3D引擎都具備這種資料繫結機制,此篇我們將構建一個3D飛機模型,展示如果將資料繫結機制運用於3D模型,同時會運用到HT的動畫機制,以及OBJ 3D模型載入等技術細節,正巧趕上剛釋出的iOS8我們終於能將基於HT for Web開發的HTML5 3D應用跑在iOS系統了。

Screen Shot 2014-10-08 at 7.45.25 PM

首選我們需要一個飛機模型,採用HT for Web構建3D模型可採用API組合各種基礎模型的方式,但今天我們將採用讀入OBJ的方式,畢竟網上已有很多不錯的現成模型素材,搜查了一番後我在www.turbosquid.com選擇了的這款免費的飛機模型,這個飛機模型是3dsmax格式,飛機模型是一體化的,由於我還需要控制機頭的螺旋槳,因此我用3dsmax做了點改造,將螺旋槳分離了機身獨立作為一個材質,同時匯出成HT for Web可讀取的OBJ格式,接下來就沒美工設計師什麼事了,剩下就全靠我們程式設計師自己的程式碼手藝活了。

Screen Shot 2014-10-08 at 8.26.18 PM

讀取OBJ檔案一般採用AJAX的方式遠端載入,這對於喜歡純前端的程式設計師來說很不爽,開發或演示個例子還得啟服務,我喜歡本地檔案開啟就能跑不受跨域安全限制,因此我們需要將OBJ的文字資訊放在在HTML或者JS程式碼中。解決這類問題有很多種方式,例如對於WebGL開發來說vertex shader和fragment shader程式碼同樣面臨這個問題,一種方式是寫成一堆的string的array然後進行join的方式,另一種方式是增加<script id=”shader-vs” type=”x-shader/x-vertex”></script>和<script id=”shader-fs” type=”x-shader/x-fragment”></script>的自定義類似script塊,然後讀取相應DOM元素的textContent來獲取文字內容。

但這兩種方式都不適合OBJ內容,因為OBJ內容太長,採用陣列方式對於成千上萬行的OBJ檔案行行加引號是不可思議的工作量(當然你可以再寫個工具幹這事),而採用<script>的方式會使得頁面的HTML程式碼太長不易閱讀編輯,我喜歡採用下面程式碼所示的這種方式,obj和mtl檔案就像普通的js檔案,可分離HTML頁面程式碼,可給多個例子複用,且沒有跨域安全問題,當然程式碼有點tricky,將function轉換成字串再擷取中間文字內容:

var flight_mtl = getRawText(function(){/*
	newmtl body
	Ns 10.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.3608 0.4353 0.2549
	Kd 0.3608 0.4353 0.2549
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	...
*/});

var flight_obj = getRawText(function(){/*
	v  -21.7990 -2.5094 -157.4279
	v  -34.5972 -20.3459 -42.9317
	v  -36.7638 -6.2029 -43.0833
	...
*/});			

function getRawText(obj){
    var text = String(obj); 
    return text.substring(14, text.length-3);
}

 以下為註冊飛機模型的程式碼,通過程式碼的註解可知我們對飛機模型做了調整,通過r3: [0, -Math.PI/2, 0]我將整體飛機模型沿著y軸旋轉了-Math.PI弧度使之朝向右邊,通過s3:[0.1, 0.1, 0.1]將飛機模型縮小了10倍。

ht.Default.loadObj(flight_obj, flight_mtl, {                    
	center: true,
	r3: [0, -Math.PI/2, 0], // make plane face right
	s3: [0.1, 0.1, 0.1], // make plane smaller
	finishFunc: function(modelMap, array, rawS3){
		if(modelMap){                            
			modelMap.propeller.r3 = {
				func: function(data){
					return [data.a('angle'), 0, 0]; 
				}                                
			};                             
			// make propeller a litter bigger
			modelMap.propeller.s3 = [1, 1.2, 1.2]; 
			modelMap.propeller.color = 'yellow';

			// add a sphere model as an indicator light
			array.push({
				shape3d: ht.Default.createSmoothSphereModel(),
				t3: [-40, 10, 0],
				s3: [6, 6, 6],
				color: {
					func: function(data){
						return data.a('light') ? 'red': 'black';
					}
				}
			});
			ht.Default.setShape3dModel('plane', array);

			createPlane(rawS3);
			createFormPane();  
		} 
	}
});

 

Screen Shot 2014-10-08 at 9.46.53 PM

飛機的螺旋槳模型繫結了data.a(‘angle’)屬性,原始螺旋槳模型有點小,通過modelMap.propeller.s3 = [1, 1.2, 1.2];在yz面做了1.2倍的放大,通過modelMap.propeller.color = ‘yellow’;將原始模型的顏色改成更顯眼的黃色,當然你也可以通過修改mtl檔案實現,甚至再將該屬性繫結資料模型進行動態變化。

飛機尾部原始模型並沒有指示燈,我們通過ht.Default.createSmoothSphereModel()用API建立了一個模型,與OBJ的模型進行了組合,指示燈的顏色通過return data.a(‘light’) ? ‘red’: ‘black’;的函式邏輯進行資料繫結,後續我們將在飛機執行過程動態變化data.a(‘light’)引數,實現飛機飛行過程指示燈的閃爍效果。

Screen Shot 2014-10-08 at 10.01.56 PM

飛行路線是通過ht.Polyline型別構建的,上圖的幾個黃色球是飛行路線Polyline物件的部分控制點,通過這幾個控制點我們甚至可以在飛機飛行過程動態改變飛行路線。

params = {
      delay: 1500,
      duration: 20000,
      easing: function(t){
           return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2));                     
      },
      action: function(v, t){
           var point = getPoint(v),
                px = point.x,
                py = point.y,
                pz = point.z,
                tangent = getTangent(v),
                tx = tangent.x,
                ty = tangent.y,
                tz = tangent.z;
           plane.p3(px, py, pz);
           plane.lookAt([px + tx, py + ty, pz + tz], 'right');  

           var camera = formPane.v('Camera');
           if(camera === 'Look At'){
                g3d.setCenter(px, py, pz);
           }
           else if(camera === 'First Person'){                           
                g3d.setEye(px - tx * 400, py - ty * 400 + 30, pz - tz * 400);
                g3d.setCenter(px, py, pz);                           
           }

           plane.a('angle', v*Math.PI*120);                       
           if(this.duration * t % 1000 > 500){
                plane.a('light', false);
           }else{
                plane.a('light', true);
           }                       
      },
      finishFunc: function(){
           animation = ht.Default.startAnim(params);
           plane.a('light', false);
      }                 
 };                              

 animation = ht.Default.startAnim(params);

 以上為飛行動畫的相關程式碼,ht.Default.startAnim可啟動Frame-Based和Time-Based兩種方式的動畫,本例中我們需要動態改變飛行的週期,同時Frame-Based的方式會導致不同硬體裝置總體執行週期差異太大,因此我們採用設定Duration的Time-Based的動畫方式。

動畫過程主要要改變飛機的位置,以及保持機頭朝向切線方向,同時在Look At的模式下,我們不斷讓HT的Graph3dView的eye屬性盯著飛機的位置,First Person模式下我們還需要改變Graph3dView的center屬性。通過if(this.duration * t % 1000 > 500)的程式碼邏輯,實現了半秒鐘改變一次light屬性的閃爍效果。

為了達到更逼真的現實效果我們定義了Easing函式,採用了easeBoth這種起始結束較慢中間過程較快的動畫函式,可參考《透過WebGL 3D看動畫Easing函式本質》文章,從而實現飛機逐漸加速啟動啟動,慢慢減速著落的效果,螺旋槳的旋轉角度也在動畫過程中根據Easing相關引數值設定,因此螺旋槳的旋轉速度也一致的放映了這種動畫效果。

Screen Shot 2014-10-08 at 10.36.35 PM

該例子綜合運用了HT for Web的多種技術功能,大家能體會到HT這種資料繫結機制靈活且強大的特點,通過資料繫結機制,我們可以動態修改從2D拓撲圖、到通用元件渲染,甚至到3D引擎的資料模型,所有圖形元素的顏色、大小和角度等引數皆可靈活控制,並且以最直觀易用的方式供程式設計師二次開發與實際業務資料繫結關聯。

最後上段該HTML5例子在iOS、Android和Mac等多平臺下的執行視訊和抓圖,有興趣的同學還可對該例子做更多有意思的改造擴充套件。http://v.youku.com/v_show/id_XNzk5MzI3MzMy.html

Screen Shot 2014-10-08 at 7.44.54 PM

 

相關文章