HT for Web (Hightopo) 使用心得(5)- 動畫的實現

一隻物聯網鯨魚發表於2023-11-29

其實,在 HT for Web 中,有多種手段可以用來實現動畫。我們這裡仍然用直升機為例,只是更換了場景。增加了巡遊過程。

使用 HT 開發的一個簡單網頁直升機巡邏動畫(Hightopo 使用心得(5))

這裡主要用到的動畫實現方式有三種:

  • setInterval
  • ht.Default.startAnim()
  • DataModel.addScheduleTask(task)

場景搭建

具體3D場景的相關概念請參考《Hightopo 使用心得(4)- 3D 場景 Graph3dView 與 Obj 模型》。

這裡的主要工作分為:3D 場景配置以及模型載入。其中 3D 場景部分的設定程式碼如下:

this.g3d = new ht.graph3d.Graph3dView();
this.g3d.setGridVisible(true);
this.g3d.setGridSize(5000);
this.g3d.setGridGap(2000);
this.g3d.setNear(10)
this.g3d.setFar(10000000)
this.g3d.addToDOM();
this.dataModel = this.dm = this.g3d.dm();

為了給直升機搭建一個逼真的環境。這裡我們增加了一個山體模型。另外,由於直升機機體與螺旋槳模型是分開的,因此需要分別載入並調整其位置讓二者合併成一個模型。

// 載入山體模型
this.mountains = await this.createObj(MODELS.MOUNTAINS.name, MODELS.MOUNTAINS.obj, MODELS.MOUNTAINS.mtl);
this.mountains.s('3d.selectable',false);
this.mountains.s('shape3d.scaleable',true);
this.mountains.setScale3d([0.01, 0.1, 0.01]);
this.mountains.setElevation(1800); // 讓山體在地面以上
// 分別載入直升機及螺旋槳模型
this.helicopterNode = await this.createObj(MODELS.HELICOPTER.name, MODELS.HELICOPTER.obj, MODELS.HELICOPTER.mtl);
this.propellerNode = await this.createObj(MODELS.PROPELLER.name, MODELS.PROPELLER.obj, MODELS.PROPELLER.mtl);
// 由於預設建立 Node 的時候,其錨點是在 [0.5, 0.5, 0.5],位置是在 [0, 0, 0]。導致模型並不在水平面以上。
let size3d = this.helicopterNode.getSize3d(); // 獲取直升機模型的 [長,寬,高]
let height = size3d[1]; // 獲取模型高度
this.helicopterNode.setPosition3d([0, height/2, 0]); // 將直升機放到地面上
this.propellerNode.setRotation3d([0.10506443461595279, 4.550746858974086, -0.007825951889059535]); // 讓螺旋槳水平
this.propellerNode.setPosition3d([0, 215, -99.00152946490829]); // 將螺旋槳放到直升機上
this.propellerNode.setHost(this.helicopterNode); // 螺旋槳吸附到直升機上
this.helicopterNode.p3(0,2000,0); // 直升機

HT for Web (Hightopo) 使用心得(5)- 動畫的實現

螺旋槳動畫 - setInterval

螺旋槳動畫比較簡單,其本質是透過不斷地修改螺旋槳節點在豎直方向(Y 軸)的角度。

/**
* 螺旋槳旋轉動畫
*
*/
startPropellerAnim(node) {
setInterval(() => {
const r3 = node.getRotation3d();
node.setRotation3d([r3[0], r3[1] + 0.4, r3[2]]); // 繞 Y 軸旋轉
}, 20);
}

建立直升機巡遊路徑

有了直升機及環境,我們需要讓直升機動起來。例如在這裡,我們計劃讓直升機圍繞山體巡邏。這裡該如何實現呢?

在 HT for Web 官方手冊中,其提供了一種實現方式,我們這裡稍微加以改造便可讓直升機圍繞山體巡邏。

HT for Web (Hightopo) 使用心得(5)- 動畫的實現

在程式碼層面,我們建立了一條三維線段(Polyline)。該線段實現的是一個圓環,懸浮在山體上面。有了這條路徑,直升機便可沿著該路徑前進實現巡遊動畫。

polyline的形狀主要由points和segments這兩個屬性描述。二者都是陣列。其中 points 可以理解成組成 polyline 所要用到的點集合,而 segments 陣列主要用來定義如何使用前面的點來組成 polyline。

points 中的每一項為 {x,y,e} 格式,需要注意的是,這裡代表高度的是 e(elevation),而不是 y。

segments 陣列裡面有5種值。分別為:

  • 1: moveTo,佔用1個點資訊,代表一個新路徑的起點
  • 2: lineTo,佔用1個點資訊,代表從上次最後點連線到該點
  • 3: quadraticCurveTo,佔用2個點資訊,第一個點作為曲線控制點,第二個點作為曲線結束點
  • 4: bezierCurveTo,佔用3個點資訊,第一和第二個點作為曲線控制點,第三個點作為曲線結束點
  • 5: closePath,不佔用點資訊,代表本次路徑繪製結束,並閉合到路徑的起始點
/**
* 建立直升機巡遊路徑
*
* @memberof Index3d
*/
createPath() {
this.g3d.setDashDisabled(false); // 顯示虛線
let height = 2000; // 線段離地高度
let dataModel = this.dataModel;
let polyline = this.polyline = new ht.Polyline();
polyline.setThickness(5); // 線段粗細
polyline.s({
'shape3d.image': 'assets/flow.png', // 貼圖
"shape3d": "cylinder", // polyline型別,這裡是圓柱。也可以是
'repeat.uv.length': 400, // 貼圖寬度
'shape3d.resolution': 1600, // 管線解析度,解析度越高越平滑
});
dataModel.add(polyline);
// 起始點
const points = [{
x: -15000,
y: 0,
e: height,
}];
const segments = [1];
// 二次曲線,佔用兩個點。生成一條弧線。下同。
points.push({
x: -15000,
y: -15000,
e: height
});
points.push({
x: 0,
y: -15000,
e: height
});
segments.push(3);
points.push({
x: 15000,
y: -15000,
e: height
});
points.push({
x: 15000,
y: 0,
e: height
});
segments.push(3);
points.push({
x: 15000,
y: 15000,
e: height
});
points.push({
x: 0,
y: 15000,
e: height
});
segments.push(3);
points.push({
x: -15000,
y: 15000,
e: height
});
points.push({
x: -15000,
y: 0,
e: height,
});
segments.push(3);
polyline.setPoints(points);
polyline.setSegments(segments);
polyline.setAnchorElevation(0)
}

直升機巡遊動畫 - ht.Default.startAnim

接下來,我們需要讓直升機沿著巡遊路徑前進。在實現的時候,我們使用了 ht.Default.startAnim() 方法。該方法我們在前幾篇文章中都用過,這裡就不再詳細介紹。

HT for Web (Hightopo) 使用心得(5)- 動畫的實現

ht.Default.startAnim() 會執行 duration 毫秒,在執行過程中,其會自動計算所需要的幀數並在每一幀都呼叫一次action 方法。也就是說,如果我們想讓直升機 40 秒圍繞路徑飛行一圈,我們只需要將 duration 設定成40*1000 毫秒,並且在每一幀拿到當前時刻 polyline 上的點的座標及方向。同時,使用該座標與方向設定直升機位置及朝向就可以實現巡遊動畫。

這裡面比較關鍵的一個方法是 g3d.getLineOffset(polyline, length * v) 。該方法會返回一個物件:{point: p.M…h.Vector3, tangent: p.M…h.Vector3}。其分別代表當前時刻 polyline 上的點的座標及放向。根據這兩個值,我們可以進一步配置直升機的位置和朝向。

/**
* 直升機沿著巡遊路徑飛行
*
* @param {number} [duration=40 * 1000]
* @memberof Index3d
*/
startFly(duration = 40 * 1000) {
const {
g3d,
polyline
} = this;
/** 獲取巡遊路徑總長度 */
let length = g3d.getLineLength(polyline);
const params = {
delay: 0,
duration,
easing: (t) => {
return t;
},
action: (v, t) => {
let offset = g3d.getLineOffset(polyline, length * v),
point = offset.point,
px = point.x,
py = point.y + 200, // 讓直升機高於polyline
pz = point.z,
tangent = offset.tangent,
tx = tangent.x,
ty = tangent.y,
tz = tangent.z;
this.helicopterNode.p3(px, py, pz);
this.helicopterNode.lookAt([px + tx, py + ty, pz + tz], 'back'); // 一個模型有6個面,這裡需要確定機頭處於哪個面
// 視角盯住直升機
if (this._cameraType == 1) {
g3d.setCenter(px, py, pz);
} else if (this._cameraType == 2) { // Camera跟隨直升機運動
g3d.setEye(px - tx * 1800 + 1000, py - ty * 1800 + 1000, pz - tz * 1800); // 讓鏡頭高於直升機並在尾部進行觀察
g3d.setCenter(px, py, pz);
}
this.helicopterNode.a('angle', v * Math.PI * 120);
},
finishFunc: () => {
ht.Default.startAnim(params);
}
};
ht.Default.startAnim(params);
}

管道流動動畫 - DataModel.addScheduleTask()

實現管道流動的動畫有多種方式,其本質是定期改變管道的貼圖偏移。

HT for Web (Hightopo) 使用心得(5)- 動畫的實現

這裡我們採用DataModel#addScheduleTask(task)實現流動動畫。DataModel#addScheduleTask(task)實際上是新增了一個排程任務。由於該方法是在 DataModel 上執行,因此在每次執行的時候,DataModel 裡面的每個 Data 都會被呼叫。我們可以在 action 引數裡面對 Data 進行過濾。DataModel#addScheduleTask(task)方法的引數task為json物件,可指定如下屬性:

  • interval:間隔毫秒數,預設值為10
  • enabled:是否啟用開關,預設為true
  • beforeAction:排程開始之前的動作函式
  • action:間隔動作函式,對DataModel上的每個data節點都會執行一次action操作
  • afterAction:排程結束之後的排程函式

另外,可以用DataModel#removeScheduleTask(task)刪除排程任務,其中task為以前新增過的排程任務物件。

/**
* 透過DataModel的addScheduleTask實現流動效果
*
* @memberof Index3d
*/
addScheduleTasks() {
const task = {
interval: 50, // 間隔毫秒數,預設值為10
enabled: true, // 是否啟用開關,預設為true
beforeAction: () => {}, // 排程開始之前的動作函式
afterAction: () => {}, // 排程結束之後的排程函式
action: (data) => { // 間隔動作函式,對DataModel上的每個data節點都會執行一次action操作
if (data.getClassName() == 'ht.Polyline') {
const offset = (data.s('shape3d.uv.offset') || [0,0]);
data.s('shape3d.uv.offset', [offset[0] + 0.1, offset[1]]);
}
}
};
this.dataModel.addScheduleTask(task);
// this.dataModel.removeScheduleTask(task); // 刪除排程任務
}

這裡我們只是舉例介紹一下DataModel#addScheduleTask(task)的用法。對於一個 DataModel 中大部分 Data 都需要動畫的時候,可以考慮使用該方法。

在程式碼執行的時候,我們可以選擇把巡遊路徑隱藏。這樣看起來直升機就是沿著一個圓形持續巡遊。

hidePath() {
this.polyline.s('3d.visible', false);
}

總結

本文介紹瞭如何透過程式碼實現一個直升機繞山巡遊的動畫,包括建立路徑和實現直升機的飛行動畫。另外,還介紹瞭如何透過DataModel#addScheduleTask(task)實現流動效果的動畫。讀完本文,你將瞭解到如何使用 HT for Web 實現各種動畫效果。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69997639/viewspace-2997906/,如需轉載,請註明出處,否則將追究法律責任。

相關文章