智能城市是一個系統。也稱爲網絡城市、數字化城市、信息城市。html
智能城市建設是一個系統工程:首先實現的是城市管理智能化,由智能城市管理系統輔助管理城市,經過管理系統人們能夠監視城市的運行,瞭解城市天天中發生的變化,以及及時的根據這些變化作出相應的管理;其次是包括智能交通、智能電力、智能安全等基礎設施的智能化,交通是一個城市的驅動,交通的暢通加速了城市的發展,經過 Web 可視化的交通管理,能夠更及時的瞭解交通狀況,作出處理;智能城市也包括智能醫療、智能家庭、智能教育等社會智能化和智能企業、智能銀行、智能商店的生產智能化,從而全面提高城市生產、管理、運行的現代化水平。node
本 demo 使用 HT for Web 產品輕量化 HTML5/WebGL 建模的方案,構建了城市建築羣場景,添加了城市道路,實現了智能城市 Web 可視化,還經過動畫模擬了城市的運行。json
demo 地址:http://www.hightopo.com/demo/intelligent-city/entry/dest/index.html安全
預覽圖:網絡
加載場景app
首先新建一個場景,並將場景添加到頁面中。dom
let dm = this.dm = new ht.DataModel(); let entryG3d = this.entryG3d = new ht.graph3d.Graph3dView(dm); entryG3d.addToDOM(); // 將場景添加到頁面中
HT 的組件通常都會嵌入 BorderPane、SplitView 和 TabView 等
容器中使用,而最外層的 HT 組件則須要用戶手工將 getView() 返回的底層 div 元素添加到頁面的 DOM 元素中,這裏須要注意的是,當父容器大小變化時,若是父容器是 BorderPane 和 SplitView 等這些 HT 預約義的容器組件,則 HT 的容器會自動遞歸調用孩子組件 invalidate 函數通知更新。但若是父容器是原生的 html 元素, 則 HT 組件沒法獲知須要更新,所以最外層的 HT 組件通常須要監聽 window的窗口大小變化事件,調用最外層組件 invalidate 函數進行更新。ide
爲了最外層組件加載填充滿窗口的方便性,HT 的全部組件都有 addToDOM 函數,其實現邏輯以下,其中 iv 是 invalidate 的簡寫:函數
addToDOM = function(){ var self = this, view = self.getView(), // 獲取組件 div style = view.style; document.body.appendChild(view); // 將組件添加到文檔對象中 style.left = '0'; style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); // 監聽窗口變化,刷新組件 }
接下來反序列化城市場景 json。工具
ht.Default.xhrLoad('scenes/園區/城市demo.json', (text) => { let json = ht.Default.parse(text); // 還原 json 字符串 let scene = json.scene; // 獲取 json 中設置的投影參數 entryG3d.setEye(scene.eye); // 設置視角 entryG3d.setCenter(scene.center); // 設置目標中心點 entryG3d.setFar(scene.far); // 設置遠端截面位置 entryG3d.setNear(scene.near); // 設置近端截面位置 dm.deserialize(text); // 場景反序列化 this.setSkyBox(entryG3d); // 設置天空球 this.initentryG3dEvent(); // 添加場景監聽事件 this.startAnimate(); // 啓動城市動畫 this.startCarAnimate(['car1Line'], dm.getDataByTag('car1')); // 啓動消防小車1動畫 this.startCarAnimate(['car2Line'], dm.getDataByTag('car2')); // 啓動消防小車2動畫 });
場景渲染
1. 環境光貼圖:將貼圖影像渲染在場景中,經過 node.s('envmap', 0.5) 來設置節點的渲染程度,主要用於地板添加物體倒影效果,也可像本 demo 同樣添加星光點綴。
dataModel.setEnvmap('環境光貼圖.png'); // 貼圖要求寬高像素爲 2^n node.s('envmap', 0.1);
對比圖:
右圖中環境光貼圖爲星光,城市中心區域有了藍色的星空色,在 demo 中旋轉場景也能看到明顯的星光變化。
2. 輝光:本是指低壓氣體中氣體放電的物理現象,用在 3D 場景中將亮色突出顯示,呈現一種發光的效果,可控制輝光強度,輝光顯示的範圍和發光閥值。
g3d.enablePostProcessing('Bloom', true); // 開啓輝光 module = g3d.getPostProcessingModule('Bloom'); module.strength = 0.18; // 強度 module.threshold = 0.62; // 閾值 module.radius = 0.4; //範圍 g3d.iv(); // 刷新拓撲
對比圖:
3. 景深:對場景中心周圍的清晰程度的控制,將周圍虛化,美化畫面,突出主體,加強透視,可控制景深閥值(周圍模糊範圍程度)。
g3d.enablePostProcessing('Dof', true); // 開啓景深 module = g3d.getPostProcessingModule('Dof'); module.aperture = 0.18; // 景深閥值 module.image= '景深貼圖.png'; // 景深貼圖 g3d.iv(); // 刷新拓撲
對比圖:
能夠看到右圖中衛星區域和最左側變得模糊。
4. 天空球:將場景模型放置在一個大的球體中,球體內部進行貼圖,來模擬天空。
node = new ht.Node() node.s({ 'shape3d':'sphere', // 球體 'shape3d.image': 'earth' // 貼圖路徑 }); node.s3(10000, 10000, 10000); g3d.dm().add(node); g3d.setSkyBox(node); // 設置天空球
動畫實現
加載後的城市場景以下圖所示:
咱們能夠看到建築物羣有各自的數據展現面板,圍繞着中心大樓有一個光圈列車模型,是模擬的城市中心列車,還有右邊一個城市衛星,如今咱們來讓數據面板數據變化,讓列車、衛星開始動起來。
動畫的實現是用調度函數來實現的(調度手冊),咱們先了解一下調度函數的用法:
HT 中調度進行的流程是,先經過 dataModel 添加調度任務,dataModel 會在調度任務指定的時間間隔到達時, 遍歷 dataModel 全部圖元回調調度任務的 action 函數,可在該函數中對傳入的 data 圖元作相應的屬性修改以達到動畫效果。
dataModel.addScheduleTask(task) 添
加調度任務,其中 task 爲 json 對象,可指定以下屬性:
interval
:間隔毫秒數,默認值爲10
dataModel.removeScheduleTask(task) 刪除調度任務,其中 task 爲之前添加過的調度任務對象。
動畫實現代碼以下:
startAnimate() { let dr = Math.PI / 180 * 2, PI2 = Math.PI * 2; let dm = this.dm; let rotateOval = dm.getDataByTag('rotateOval'); // 獲取城市列車模型 let logo = dm.getDataByTag('logo'); // 獲取城市衛星模型
// 設置動畫參數 let roatateTask = this.roatateTask = { interval: 100, action: function(data){ if(data === rotateOval || data === logo){ data.setRotation((data.getRotation() - dr) % PI2); } } }; dm.addScheduleTask(roatateTask); // 將動畫加入到調度任務中
// 獲取建築物數據面板 let dglsd = dm.getDataByTag('dglsd'); // 戴谷嶺隧道 let xlsd = dm.getDataByTag('xlsd'); // 杏林隧道 let xmg = dm.getDataByTag('xmg'); // 廈門港 let jcglzx = dm.getDataByTag('jcglzx'); // 機場管理中心
// 模擬建築物數據面板的動態展現 let valueChangeTask = this.valueChangeTask = { interval: 1000, action: function(data){ if(data === dglsd){ data.a('carHour', util.randomNumBetween(3000, 6000)); } if(data === xlsd){ data.a('carHour', util.randomNumBetween(3000, 6000)); } if(data === xmg){ data.a('ttl', util.randomNumBetween(3, 10)); data.a('zy', util.randomNumBetween(0, 100)); } if(data === jcglzx){ data.a('sskll', util.randomNumBetween(500, 2000)); } } }; dm.addScheduleTask(valueChangeTask); // 將數據變化加入到調度任務中 }
實現的動畫效果以下圖:
demo 還模擬了消防車趕往火災發生地,動畫以下:
消防車的行駛用到了 ht.Default.startAnim,咱們先來了解一下:
ht.Default.startAnim({ frames: 12, // 動畫幀數 interval: 10, // 動畫幀間隔毫秒數 easing: function(t){ return t * t; }, // 動畫緩動函數,默認採用 ht.Default.animEasing finishFunc: function(){ console.log('Done!') }, // 動畫結束後調用的函數。 action: function(v, t){ // action函數必須提供,實現動畫過程當中的屬性變化。 node.setPosition( // 此例子展現將節點`node`從位置`p1`動畫到位置`p2`。 p1.x + (p2.x - p1.x) * v, p1.y + (p2.y - p1.y) * v ); } });
以上爲 Frame-Based 方式動畫, 這種方式用戶經過指定 frames 動畫幀數,以及 interval 動畫幀間隔參數控制動畫效果。
ht.Default.startAnim({ duration: 500, // 動畫週期毫秒數,默認採用`ht.Default.animDuration` action: function(v, t){ ... } });
以上爲 Time-Based 方式動畫,該方式用戶只須要指定 duration 的動畫週期的毫秒數便可,HT 將在指定的時間週期內完成動畫。
因爲 js 語言沒法精確控制 interval 時間間隔, 採用 Frame-Based 不能精確控制動畫時間週期,即便相同的 frames 和 interval 參數在不一樣的環境,可能會出現動畫週期差別較大的問題, 所以 HT 默認採用 Time-based 的方式,若是不設置 duration 和 frames 參數,則 duration 參數將被系統自動設置爲 ht.Default.animDuration 值。
消防車行駛實現代碼以下:
startCarAnimate(polylineTagArr, airNode) { // 傳入消防車行駛路線 tag(惟一標籤)組和消防車模型節點 let dm = this.dm; let entryG3d = this.entryG3d; let curIndex = 0; let polyline = dm.getDataByTag(polylineTagArr[curIndex]); // 獲取消防路線 ht.PolyLine(http://www.hightopo.com/guide/guide/core/shape/ht-shape-guide.html#ref_different) let prePos = airNode.p3(); // 獲取節點初始位置 let preRotate = airNode.r3(); // 獲取節點初始旋轉角度 let lineLength = entryG3d.getLineLength(polyline); // 獲取管道長度 let params = { // 設置消防車行駛動畫參數 duration: 10000, easing: function(t){ return t * t; }, action: function(v, t){ let offset = entryG3d.getLineOffset(polyline, lineLength * v), // 獲取管道指定比例的偏移信息 point = offset.point, px = point.x, py = point.y, pz = point.z, tangent = offset.tangent, tx = tangent.x, ty = tangent.y, tz = tangent.z; airNode.p3(px, py, pz); // 移動消防車到下一位置 airNode.lookAt([px + tx, py + ty, pz + tz], 'front'); // 消防車沿着管道方向轉向 }, finishFunc: function(){ if(curIndex < polylineTagArr.length - 1) { // 進入下一段路線 curIndex++; } else { curIndex = 0; // 返回第一段路線 } if(curIndex === 0) { // 消防車返回原點 airNode.p3(prePos); airNode.r3(preRotate); } polyline = dm.getDataByTag(polylineTagArr[curIndex]); lineLength = entryG3d.getLineLength(polyline); this.carAni = ht.Default.startAnim(params); // 執行下一段行駛動畫 } }; this.carAni = ht.Default.startAnim(params); }
消防車的行駛可總結爲先獲取行駛道路的管線長度信息,計算必定比例的管線偏移點,移動消防車位置到計算的偏移點,調整消防車模型的朝向,計算下一偏移點進行位移,直到到達當前管線尾,而後獲取下一管線的長度信息,繼續進行偏移。
代碼中提到的三個方法:getLineLength、getLineOffset、lookAt,咱們來了解一下。
1. g3d.getLineLength(edgeOrPolyLine)
根據參數 edgeOrPolyLine 方法命名咱們就能夠知道此方法是獲取連線或管道的長度。
2. g3d.getLineOffset(edgeOrPolyLine, offset)
此方法獲取連線或者管道的偏移信息,返回 { point, tangent },其中 point 是 3D 座標,tangent 是 point 點沿當前線的切線向量。
3. node.lookAt(point, direction)
將節點的某一面朝向空間某座標點。
消防車行駛轉向示意圖:
1. 起始位置;
2. 經過 setPosition3d(point.x, point.y, point.z) 到達下一位置,紅線爲 tangent 切線向量;
3. 經過 node.lookAt([point.x + tangent.x, point.y + tangent.y, point.z + tangent.z], 'front'); 將車頭擺向切線方向;
4.5. 重複以上步驟。
其中 [point.x + tangent.x, point.y + tangent.y, point.z + tangent.z] 即爲目標點 target 的座標。
智能城市系統還包括智能樓宇的管理:
http://www.hightopo.com/demo/ht-smart-building/
地鐵是一個城市的核心交通工具,地鐵站的管理也必不可少: