其實(shí),在 HT for Web 中,有多種手段可以用來(lái)實(shí)現(xiàn)動(dòng)畫。我們這里仍然用直升機(jī)為例,只是更換了場(chǎng)景。增加了巡游過(guò)程。
使用 HT 開發(fā)的一個(gè)簡(jiǎn)單網(wǎng)頁(yè)直升機(jī)巡邏動(dòng)畫(Hightopo 使用心得(5))
這里主要用到的動(dòng)畫實(shí)現(xiàn)方式有三種:
ht.Default.startAnim()
DataModel.addScheduleTask(task)
場(chǎng)景搭建
具體3D場(chǎng)景的相關(guān)概念請(qǐng)參考《Hightopo 使用心得(4)- 3D 場(chǎng)景 Graph3dView 與 Obj 模型》。
這里的主要工作分為:3D 場(chǎng)景配置以及模型加載。其中 3D 場(chǎng)景部分的設(shè)置代碼如下:
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();
為了給直升機(jī)搭建一個(gè)逼真的環(huán)境。這里我們?cè)黾恿艘粋€(gè)山體模型。另外,由于直升機(jī)機(jī)體與螺旋槳模型是分開的,因此需要分別加載并調(diào)整其位置讓二者合并成一個(gè)模型。
// 加載山體模型
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); // 讓山體在地面以上
// 分別加載直升機(jī)及螺旋槳模型
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);
// 由于默認(rèn)創(chuàng)建 Node 的時(shí)候,其錨點(diǎn)是在 [0.5, 0.5, 0.5],位置是在 [0, 0, 0]。導(dǎo)致模型并不在水平面以上。
let size3d = this.helicopterNode.getSize3d(); // 獲取直升機(jī)模型的 [長(zhǎng),寬,高]
let height = size3d[1]; // 獲取模型高度
this.helicopterNode.setPosition3d([0, height/2, 0]); // 將直升機(jī)放到地面上
this.propellerNode.setRotation3d([0.10506443461595279, 4.550746858974086, -0.007825951889059535]); // 讓螺旋槳水平
this.propellerNode.setPosition3d([0, 215, -99.00152946490829]); // 將螺旋槳放到直升機(jī)上
this.propellerNode.setHost(this.helicopterNode); // 螺旋槳吸附到直升機(jī)上
this.helicopterNode.p3(0,2000,0); // 直升機(jī)
螺旋槳?jiǎng)赢?- setInterval
螺旋槳?jiǎng)赢嫳容^簡(jiǎn)單,我們?cè)凇禜ightopo 使用心得(4)- 3D 場(chǎng)景 Graph3dView 與 Obj 模型_CodingInProgress的博客-CSDN博客》中已經(jīng)提過(guò)。其本質(zhì)是通過(guò)不斷地修改螺旋槳節(jié)點(diǎn)在豎直方向(Y 軸)的角度。
/**
* 螺旋槳旋轉(zhuǎn)動(dòng)畫
*
*/
startPropellerAnim(node) {
setInterval(() => {
const r3 = node.getRotation3d();
node.setRotation3d([r3[0], r3[1] + 0.4, r3[2]]); // 繞 Y 軸旋轉(zhuǎn)
}, 20);
}
創(chuàng)建直升機(jī)巡游路徑
有了直升機(jī)及環(huán)境,我們需要讓直升機(jī)動(dòng)起來(lái)。例如在這里,我們計(jì)劃讓直升機(jī)圍繞山體巡邏。這里該如何實(shí)現(xiàn)呢?
在 HT for Web 官方手冊(cè)中,其提供了一種實(shí)現(xiàn)方式,我們這里稍微加以改造便可讓直升機(jī)圍繞山體巡邏。
在代碼層面,我們創(chuàng)建了一條三維線段(Polyline)。該線段實(shí)現(xiàn)的是一個(gè)圓環(huán),懸浮在山體上面。有了這條路徑,直升機(jī)便可沿著該路徑前進(jìn)實(shí)現(xiàn)巡游動(dòng)畫。
polyline的形狀主要由points和segments這兩個(gè)屬性描述。二者都是數(shù)組。其中 points 可以理解成組成 polyline 所要用到的點(diǎn)集合,而 segments 數(shù)組主要用來(lái)定義如何使用前面的點(diǎn)來(lái)組成 polyline。
points 中的每一項(xiàng)為 {x,y,e} 格式,需要注意的是,這里代表高度的是 e(elevation),而不是 y。
segments 數(shù)組里面有5種值。分別為:
1: moveTo,占用1個(gè)點(diǎn)信息,代表一個(gè)新路徑的起點(diǎn)
2: lineTo,占用1個(gè)點(diǎn)信息,代表從上次最后點(diǎn)連接到該點(diǎn)
3: quadraticCurveTo,占用2個(gè)點(diǎn)信息,第一個(gè)點(diǎn)作為曲線控制點(diǎn),第二個(gè)點(diǎn)作為曲線結(jié)束點(diǎn)
4: bezierCurveTo,占用3個(gè)點(diǎn)信息,第一和第二個(gè)點(diǎn)作為曲線控制點(diǎn),第三個(gè)點(diǎn)作為曲線結(jié)束點(diǎn)
5: closePath,不占用點(diǎn)信息,代表本次路徑繪制結(jié)束,并閉合到路徑的起始點(diǎn)
/**
* 創(chuàng)建直升機(jī)巡游路徑
*
* @memberof Index3d
*/
createPath() {
this.g3d.setDashDisabled(false); // 顯示虛線
let height = 2000; // 線段離地高度
let dataModel = this.dataModel;
let polyline = this.polyline = new ht.Polyline();
polyline.setThickness(5); // 線段粗細(xì)
polyline.s({
'shape3d.image': 'assets/flow.png', // 貼圖
"shape3d": "cylinder", // polyline類型,這里是圓柱。也可以是
'repeat.uv.length': 400, // 貼圖寬度
'shape3d.resolution': 1600, // 管線分辨率,分辨率越高越平滑
});
dataModel.add(polyline);
// 起始點(diǎn)
const points = [{
x: -15000,
y: 0,
e: height,
}];
const segments = [1];
// 二次曲線,占用兩個(gè)點(diǎn)。生成一條弧線。下同。
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)
}
直升機(jī)巡游動(dòng)畫 - ht.Default.startAnim
接下來(lái),我們需要讓直升機(jī)沿著巡游路徑前進(jìn)。在實(shí)現(xiàn)的時(shí)候,我們使用了 ht.Default.startAnim() 方法。該方法我們?cè)谇皫灼恼轮卸加眠^(guò),這里就不再詳細(xì)介紹。
ht.Default.startAnim() 會(huì)執(zhí)行 duration 毫秒,在執(zhí)行過(guò)程中,其會(huì)自動(dòng)計(jì)算所需要的幀數(shù)并在每一幀都調(diào)用一次action 方法。也就是說(shuō),如果我們想讓直升機(jī) 40 秒圍繞路徑飛行一圈,我們只需要將 duration 設(shè)置成40*1000 毫秒,并且在每一幀拿到當(dāng)前時(shí)刻 polyline 上的點(diǎn)的坐標(biāo)及方向。同時(shí),使用該坐標(biāo)與方向設(shè)置直升機(jī)位置及朝向就可以實(shí)現(xiàn)巡游動(dòng)畫。
這里面比較關(guān)鍵的一個(gè)方法是 g3d.getLineOffset(polyline, length * v) 。該方法會(huì)返回一個(gè)對(duì)象:{point: p.M…h(huán).Vector3, tangent: p.M…h(huán).Vector3}。其分別代表當(dāng)前時(shí)刻 polyline 上的點(diǎn)的坐標(biāo)及放向。根據(jù)這兩個(gè)值,我們可以進(jìn)一步配置直升機(jī)的位置和朝向。
/**
* 直升機(jī)沿著巡游路徑飛行
*
* @param {number} [duration=40 * 1000]
* @memberof Index3d
*/
startFly(duration = 40 * 1000) {
const {
g3d,
polyline
} = this;
/** 獲取巡游路徑總長(zhǎng)度 */
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, // 讓直升機(jī)高于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'); // 一個(gè)模型有6個(gè)面,這里需要確定機(jī)頭處于哪個(gè)面
// 視角盯住直升機(jī)
if (this._cameraType == 1) {
g3d.setCenter(px, py, pz);
} else if (this._cameraType == 2) { // Camera跟隨直升機(jī)運(yùn)動(dòng)
g3d.setEye(px - tx * 1800 + 1000, py - ty * 1800 + 1000, pz - tz * 1800); // 讓鏡頭高于直升機(jī)并在尾部進(jìn)行觀察
g3d.setCenter(px, py, pz);
}
this.helicopterNode.a('angle', v * Math.PI * 120);
},
finishFunc: () => {
ht.Default.startAnim(params);
}
};
ht.Default.startAnim(params);
}
管道流動(dòng)動(dòng)畫 - DataModel.addScheduleTask()
實(shí)現(xiàn)管道流動(dòng)的動(dòng)畫有多種方式,其本質(zhì)是定期改變管道的貼圖偏移。
這里我們采用DataModel#addScheduleTask(task)實(shí)現(xiàn)流動(dòng)動(dòng)畫。DataModel#addScheduleTask(task)實(shí)際上是添加了一個(gè)調(diào)度任務(wù)。由于該方法是在 DataModel 上執(zhí)行,因此在每次執(zhí)行的時(shí)候,DataModel 里面的每個(gè) Data 都會(huì)被調(diào)用。我們可以在 action 參數(shù)里面對(duì) Data 進(jìn)行過(guò)濾。DataModel#addScheduleTask(task)方法的參數(shù)task為json對(duì)象,可指定如下屬性:
interval:間隔毫秒數(shù),默認(rèn)值為10
enabled:是否啟用開關(guān),默認(rèn)為true
beforeAction:調(diào)度開始之前的動(dòng)作函數(shù)
action:間隔動(dòng)作函數(shù),對(duì)DataModel上的每個(gè)data節(jié)點(diǎn)都會(huì)執(zhí)行一次action操作
afterAction:調(diào)度結(jié)束之后的調(diào)度函數(shù)
另外,可以用DataModel#removeScheduleTask(task)刪除調(diào)度任務(wù),其中task為以前添加過(guò)的調(diào)度任務(wù)對(duì)象。
/**
* 通過(guò)DataModel的addScheduleTask實(shí)現(xiàn)流動(dòng)效果
*
* @memberof Index3d
*/
addScheduleTasks() {
const task = {
interval: 50, // 間隔毫秒數(shù),默認(rèn)值為10
enabled: true, // 是否啟用開關(guān),默認(rèn)為true
beforeAction: () => {}, // 調(diào)度開始之前的動(dòng)作函數(shù)
afterAction: () => {}, // 調(diào)度結(jié)束之后的調(diào)度函數(shù)
action: (data) => { // 間隔動(dòng)作函數(shù),對(duì)DataModel上的每個(gè)data節(jié)點(diǎn)都會(huì)執(zhí)行一次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); // 刪除調(diào)度任務(wù)
}
這里我們只是舉例介紹一下DataModel#addScheduleTask(task)的用法。對(duì)于一個(gè) DataModel 中大部分 Data 都需要?jiǎng)赢嫷臅r(shí)候,可以考慮使用該方法。
在代碼執(zhí)行的時(shí)候,我們可以選擇把巡游路徑隱藏。這樣看起來(lái)直升機(jī)就是沿著一個(gè)圓形持續(xù)巡游。
hidePath() {
this.polyline.s('3d.visible', false);
}
總結(jié)
本文介紹了如何通過(guò)代碼實(shí)現(xiàn)一個(gè)直升機(jī)繞山巡游的動(dòng)畫,包括創(chuàng)建路徑和實(shí)現(xiàn)直升機(jī)的飛行動(dòng)畫。另外,還介紹了如何通過(guò)DataModel#addScheduleTask(task)實(shí)現(xiàn)流動(dòng)效果的動(dòng)畫。讀完本文,你將了解到如何使用 HT for Web 實(shí)現(xiàn)各種動(dòng)畫效果。
審核編輯 黃宇
-
數(shù)字孿生
+關(guān)注
關(guān)注
4文章
1325瀏覽量
12254
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論