今天為大家帶來一個很酷的作品,依然運用了強大的 [HT for Web] 的 3D 圖形組件,動作流暢性能好,大家可以先來欣賞一下效果!
點我進入!
整體風格為科技金屬風,製作精良,由於上傳 gif 大小有限制,所以務必打開鏈接查看細節演示!
做完場景後,首先我們要對它進行一些基本的設置,如:
// 設置 camera 的位置 gv.setEye([457, 9047, 434]) // 設置中心點位置 gv.setCenter([-4, -1, 0]) // 設置遠端距離 gv.setFar(500000)
設置後可以讓場景在反序列化後能夠顯示出我們想要的展示角度,設置遠端位置能夠避免造成場景顯示不完全等問題。
為了使其看起來有一個進入的過程,我們給場景增加一個入場的動畫來增色:
ht.Default.startAnim({ duration: 3000, // 動畫週期毫秒數,默認採用`ht.Default.animDuration` action: function (v) { // action 函數必須提供,實現動畫過程中的屬性變化。 gv.setEye([gv.getEye()[0] + (1117 - gv.getEye()[0]) * (v / 5), gv.getEye()[1] + (450 - gv.getEye()[1]) * (v / 5), gv.getEye()[2] + (1139 - gv.getEye()[2]) * (v / 5)]) }, finishFunc: function () { // 動畫結束後調用的函數。 gv.scene = { eye: ht.Default.clone(gv.getEye()), center: ht.Default.clone(gv.getCenter()), far: ht.Default.clone(gv.getFar()), near: ht.Default.clone(gv.getNear()) } } })
這個動畫我們的思路是通過改變 camera 的位置來的實現,使用動畫函數我們可以在指定的時間週期內完成動畫,可理解為將某些屬性由起始值逐漸變到目標值的過程。通過在 action 函數中我們對 carmera 進行細緻地調整,就可以實現完美的入場效果了。finishFunc 函數中我們做了一個複製的操作,目的是要記住這個位置,以便於我們後面的功能實現,這個稍後會提到。
對了,我們還要對整個場景的視角及範圍做限制:
var mapInteractor = new ht.graph3d.MapInteractor(gv) gv.setInteractors([ mapInteractor ]) gv.mp(function (e) { if (e.property === "eye") { if (gv.getEye()[0] > 3500) { gv.getEye()[0] = 3500 } if (gv.getEye()[0] < -3500) { gv.getEye()[0] = -3500 } if (gv.getEye()[1] > 9000) { gv.getEye()[1] = 9000 } if (gv.getEye()[2] > 3500) { gv.getEye()[2] = 3500 } if (gv.getEye()[2] < -3500) { gv.getEye()[2] = -3500 } } })
這樣可以限制翻轉到場景底面,然後再對 eye 做限制防止在拉遠的時候超出天空球包裹的範圍。
接下來我們要加一個場景視角複位的功能:
gv.mi(function (e) { if (e.kind === doubleClickBackground) { gv.moveCamera(gv.scene.eye, gv.scene.center, true) } ... })
事件監聽一下,在雙擊的時候通過 moveCamera() 來移動中心點的位置,坐標就是我們在入場動畫的操作中記錄的位置。
為了加強性能及便利性,我們在點擊事件中再添加一個控制面板開關的的邏輯,這樣可以簡約化顯示:
if (e.kind === clickData) { if (e.data.getTag() === 按鈕) { var status = dm.getDataByTag(面板1).s(3d.visible) for (var i = 1; i <= 10; i++) { dm.getDataByTag(面板 + i).s(3d.visible, !status) } } }
通過對 2D 面板的屬性改變來實現如下效果:
面板數值的變化也通過綁定的屬性來修改,為了做演示,我用一些隨機數來代替,這裡就不多說了。
然後我們要把管道的流動、履帶的運行、迴轉窯的運動以及磨輪轉動等動畫先實現出來:
function flow(name1, name2) { for (var i = 1; i <= 6; i++) { // uv 偏移 if (name2) { dm.getDataByTag(name2 + i).s(shape3d.uv.offset, [dm.getDataByTag(name2 + i).s(shape3d.uv.offset)[0] + 0.005, dm.getDataByTag(name2 + i).s(shape3d.uv.offset)[1]]) } // 設默認值 else { dm.getDataByTag(name1 + i).s(shape3d.uv.offset, [0,0]) } } } // 儲料罐 function tank(name, num, v) { for (var i = 1; i <= 8; i++) { dm.getDataByTag(name + i).setScaleTall(dm.getDataByTag(name + i).getScaleTall() + (num[i - 1] - dm.getDataByTag(name + i).getScaleTall()) * v) } } // 滾輪 function roller(name) { for (var i = 1; i <= 12; i++) { if (i % 2 === 0) { value = -0.1 } else { value = 0.1 } if (i <= 8) { dm.getDataByTag(name + i).r3(dm.getDataByTag(name + i).r3()[0], dm.getDataByTag(name + i).r3()[1] + value, dm.getDataByTag(name + i).r3()[2]) } else { dm.getDataByTag(name + i).r3(dm.getDataByTag(name + i).r3()[0] + value, dm.getDataByTag(name + i).r3()[1], dm.getDataByTag(name + i).r3()[2]) } } } anim() function anim() { var num = [] for (var i = 1; i <= 8; i++) { num.push(Math.random() * 6) } ht.Default.startAnim({ duration: 1000, action: function (v, t) { dm.getDataByTag(流動2).r3(dm.getDataByTag(流動2).r3()[0] - 0.1, dm.getDataByTag(流動2).r3()[1], dm.getDataByTag(流動2).r3()[2]) flow(流動, 流動) tank(儲料罐, num, v) roller(滾輪) }, finishFunc: function () { anim() } }) }
我把他們統一放在一個動畫函數中循環播放,都是一些比較簡單的動畫,通過使高度、角度等屬性的變化來實現相應的動畫效果,如代碼所示不一一細述。這裡我稍微說一下關於這個管道和履帶流動的實現思路,我是利用了調整 UV 貼圖來完成的。
什麼是 UV ?通俗的講,UV 就是把三維立體模型的外表面剝離下來,展開鋪平成二維平面狀態,以便進行貼圖繪製,就如同香煙盒上的包裝圖案其實是在紙盒片狀態下印刷完成的一樣。
首先我們需要繪製一張二方連續貼圖(左右或上下可以無縫銜接的貼圖),並且依照場景中管道和傳送帶流動的方向,將 UV 展成長條狀,與貼圖相匹配。
然後我們在通過代碼驅動 UV 向 U 軸的正值方向偏移一個象限,並無限循環這一動作。回到三維場景中,你就會神奇的發現,管道和傳送帶在不間斷的流動著!
最後我們來完成卡車的運行動畫,整體流程先設計好:
var truck1 = dm.getDataByTag(卡車1) var truck2 = dm.getDataByTag(卡車2) var truck3 = dm.getDataByTag(卡車3) var cargo1 = dm.getDataByTag(貨鬥1) var cargo2 = dm.getDataByTag(貨鬥2) var coal = dm.getDataByTag(貨1) var limestone = dm.getDataByTag(貨2) var panel1 = dm.getDataByTag(面板8) var panel2 = dm.getDataByTag(面板9) anim1() // 出發 function anim1() { ht.Default.startAnim({ duration: 4000, easing: function (t) { return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2)) }, action: function (v, t) { truck1.p3(truck1.p3()[0], truck1.p3()[1], truck1.p3()[2] + (700 - truck1.p3()[2]) * (v / 10)) truck2.p3(truck2.p3()[0], truck2.p3()[1], truck2.p3()[2] + (700 - truck2.p3()[2]) * (v / 5)) truck3.p3(truck3.p3()[0] + (300 - truck3.p3()[0]) * (v / 20), truck3.p3()[1], truck3.p3()[2]) }, finishFunc: function () { anim2() } }) } // 掉頭 function anim2() { ht.Default.startAnim({ duration: 1000, action: function (v, t) { truck1.r3(truck1.r3()[0], truck1.r3()[1] + (180 * Math.PI / 180 - truck1.r3()[1]) * v, truck1.r3()[2]) truck2.r3(truck2.r3()[0], truck2.r3()[1] + (180 * Math.PI / 180 - truck2.r3()[1]) * v, truck2.r3()[2]) truck3.r3(truck3.r3()[0], truck3.r3()[1] + (-90 * Math.PI / 180 - truck3.r3()[1]) * v, truck3.r3()[2]) }, finishFunc: function () { anim3() } }) } // 卸貨 function anim3() { ht.Default.startAnim({ duration: 2000, action: function (v, t) { cargo1.r3(cargo1.r3()[0] + (70 * Math.PI / 180 - cargo1.r3()[0]) * v, cargo1.r3()[1], cargo1.r3()[2]) cargo2.r3(cargo2.r3()[0] + (70 * Math.PI / 180 - cargo2.r3()[0]) * v, cargo2.r3()[1], cargo2.r3()[2]) panel1.a(進度值, panel1.a(進度值) + (0 - panel1.a(進度值)) * v) panel2.a(進度值, panel2.a(進度值) + (0 - panel2.a(進度值)) * v) panel1.a(重量, panel1.a(進度值).toFixed(1)) panel2.a(重量, panel2.a(進度值).toFixed(1)) }, finishFunc: function () { coal.s(3d.visible, false) limestone.s(3d.visible, false) anim4() } }) } // 卸貨 function anim4() { ht.Default.startAnim({ duration: 2000, action: function (v, t) { cargo1.r3(cargo1.r3()[0] + (0 * Math.PI / 180 - cargo1.r3()[0]) * v, cargo1.r3()[1], cargo1.r3()[2]) cargo2.r3(cargo2.r3()[0] + (0 * Math.PI / 180 - cargo2.r3()[0]) * v, cargo2.r3()[1], cargo2.r3()[2]) }, finishFunc: function () { anim5() } }) } // 返回 function anim5() { ht.Default.startAnim({ duration: 4000, easing: function (t) { return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2)) }, action: function (v, t) { truck1.p3(truck1.p3()[0], truck1.p3()[1], truck1.p3()[2] + (1180 - truck1.p3()[2]) * (v / 10)) truck2.p3(truck2.p3()[0], truck2.p3()[1], truck2.p3()[2] + (1180 - truck2.p3()[2]) * (v / 5)) truck3.p3(truck3.p3()[0] + (1180 - truck3.p3()[0]) * (v / 20), truck3.p3()[1], truck3.p3()[2]) }, finishFunc: function () { anim6() } }) } // 掉頭 function anim6() { ht.Default.startAnim({ duration: 1000, action: function (v, t) { truck1.r3(truck1.r3()[0], truck1.r3()[1] + (0 * Math.PI / 180 - truck1.r3()[1]) * v, truck1.r3()[2]) truck2.r3(truck2.r3()[0], truck2.r3()[1] + (0 * Math.PI / 180 - truck2.r3()[1]) * v, truck2.r3()[2]) truck3.r3(truck3.r3()[0], truck3.r3()[1] + (90 * Math.PI / 180 - truck3.r3()[1]) * v, truck3.r3()[2]) }, finishFunc: function () { panel1.a(進度值, 1) panel2.a(進度值, 1) panel1.a(重量, 10) panel2.a(重量, 10) coal.s(3d.visible, true) limestone.s(3d.visible, true) anim1() } }) }
這個是我實現卡車的整個運作流程的完整代碼,分別由幾段動畫協調組合而成,只要搞清楚順序以及每一個動作實現的邏輯並不難辦到,無非就是方向、角度和距離的一些計算,還有面板進度條同步的設置。
經過我們的努力後,一個炫酷專業的工廠流程系統的演示我們就完成了!
在互聯網+ 概念飛速發展的今天,有太多的領域在等待著我們去挖掘,HT for Web 非常適用於各種的智慧建築,監控系統以及電力、燃氣等工業自動化 ( HMI / SCADA ) 領域。希望看了我的這篇博客,大家能有所啟發,挑戰更多的不可能!