基于 HTML5 WebGL 构建智能数字化城市 3D 全景

前言

自 2011 年我国城镇化率首次突破 50% 以来,《新型城镇化发展规划》将智慧城市列为我国城市发展的三大目标之一,并提出到 2020 年,建成一批特色鲜明的智慧城市。截至现今,全国 95% 的副省级以上城市、76% 的地级以上城市,总计约 500 多个城市提出或在建智慧城市。

基于这样的背景,本系统采用 Hightopo 的  HT for Web  产品来构造轻量化的 智慧城市 3D 可视化场景,通过三个角度的转换,更清晰让我们感知到 5G 时代下数字化智能城市的魅力

预览地址:HT 智慧城市

整体预览图

第一个视角下,城市以市中心为圆心缓缓浮现,市中心就如同整座城的大脑

第二个视角下,在楼房间穿过,细致的感受这城市的面貌

第三个视角下,鸟瞰整座城,体会智慧城市带来的不可思议的欣喜

是不是觉得有些神奇,我们接下来就是对项目的具体分析,手把手教你如何搭建一个自己心中的梦想城市

场景搭建

该系统中的大部分模型都是通过 3dMax 建模生成的,该建模工具可以导出 obj 与 mtl 文件,在 HT 中可以通过解析 obj 与 mtl 文件来生成 3D 场景中的所有复杂模型,(当然如果是某些简单的模型可以直接使用 HT 来绘制,这样会比 obj 模型更轻量化,所以大部分简单的模型都是采用 HT for Web 产品轻量化 HTML5/WebGL 建模的方案)我们先看下项目结构,源码都在 src 文件夹中

storage 保存的便是 3D 场景文件。 index.js 是 src 下的入口文件,创建了一个 由 main.js 中导出的 Main 类,Main 类创建了一个 3D 画布,用于绘制我们的 3D 场景,如下

 1 import event from ‘../util/NotifierManager‘;
 2 import Index3d from ‘./3d/Index3d‘;
 3 import { INDEX, EVENT_SWITCH_VIEW } from ‘../util/constant‘;
 4
 5 export default class Main {
 6     constructor() {
 7         let g3d = this.g3d = new ht.graph.Graph3dView(),
 8
 9         //将3d图纸添加到dom对象中
10         g3d.addToDOM();
11
12         this.event = event;
13         //创建一个Index3d类,作为场景初始化
14         this.index3d = new Index3d(g3d);
15         //调用switch方法派发EVENT_SWITCH_VIEW事件,并传入事件类型 INDEX
16         this.switch(INDEX);
17     }
18     switch(key = INDEX) {
19         event.fire(EVENT_SWITCH_VIEW, key);
20     }
21     //
22 }

我们用  new ht.graph.Graph3dView()  的方式创建了一个 3D 画布,画布的顶层是 canvas 。并创建了一个 index3d 对象,看到后面我们就能知道其实这一步就如同我们把场景“画”上去。在 main 对象中我们还引用了 util 下的 NotifierManager 文件,这个文件中的 event 对象为穿插在整个项目中事件总线,使用了 HT 自带的事件派发器,可以很方便的手动的订阅事件和派发事件,感兴趣可以进一步了解  HT 入门手册 ,下面便是文件内容

 1 class NotifierManager {
 2     constructor() {
 3         this._eventMap ={};
 4     }
 5
 6     add(key, func, score, first = false) {
 7         let notify = this._eventMap[key];
 8         if (!notify) notify = this._eventMap[key] = new ht.Notifier();
 9
10         notify.add(func, score, first);
11     }
12
13     remove(key, func, score) {
14         const notify = this._eventMap[key];
15         if (!notify) return;
16
17         notify.remove(func, score);
18     }
19
20     fire(key, e) {
21         const notify = this._eventMap[key];
22         if (!notify) return;
23
24         notify.fire(e);
25     }
26 }
27
28 const event = new NotifierManager();
29 export default event;

notify.fire() 和 notify.add() 分别是派发和订阅事件,类似于设计模式中的订阅者模式,我们很清楚的能看到,NotifierManager 类就是对 HT 原有的派发器做了一个简单地封装 ,并在创建 main 对象的时候,调用event.fire() 自动派发了 EVENT_SWITCH_VIEW 这一事件并且传入了事件类型 Index 。

画布我们有了,接下来我们就应在画布上“画”上我们的 3D 场景了。上面我们也说过了这一步由 new Index3d() 实现的, 那么它是如何实现 “画” 这一步骤的呢?

我们看看较为重要的两个文件 ui 文件夹下的 Index3d 文件和 View 文件,两个文件分别导出了 Index3d 和 View 两个类, Inde3d 类继承于 View 类,我们先来看一下 View 类的实现

 1 import event from "../util/NotifierManager";
 2 import util from ‘../util/util‘;
 3 import { EVENT_SWITCH_VIEW } from "../util/constant";
 4
 5 export default class View {
 6     constructor(view) {
 7         this.url = ‘‘;
 8         this.key = ‘‘;
 9         this.active = false;
10         this.view = view;
11         this.dm = view.dm();
12
13         event.add(EVENT_SWITCH_VIEW, (key) => {
14             this.handleSwitch(key);
15         });
16     }
17     handleSwitch(key) {
18         if (key === this.key) {
19             if (!this.active) {
20                 this.active = true;
21                 this.onUp();
22             }
23             this.dm.clear();
24             util.deserialize(this.view, this.url, this.onPostDeserialize.bind(this));
25         }
26         // 目前是这个场景,执行 tearDown
27         else if (this.active) {
28             this.onDown();
29             this.active = false;
30         }
31     }
32     /**
33      * 加载这个场景前调用
34      */
35     onUp() {
36     }
37     /**
38      * 离开这个场景时会调用
39      */
40     onDown() {
41     }
42     /**
43      * 加载完场景处理
44      */
45     onPostDeserialize() {
46         console.log(this)
47     }
48
49 }

其它内容我们就不做过多阐述了,主要说一下我们加载场景使用的 deserialize 方法,我们打开 util 下的 util 文件找到这个方法

 1 deserialize: (function() {
 2  let cacheMap = {};
 3  /**
 4   * 加载 json 并反序列化
 5   *
 6  */
 7  return function(view, url, cb, notUseCache) {
 8  let json, cache = !notUseCache;
 9  if (!notUseCache) {
10     json = cacheMap[url];
11  }
12  else {
13    cache = false;
14  }
15  // 不使用缓存,重新加载
16  view.deserialize(json || url, (json, dm, view, list) => {
17    cacheMap[url] = json;
18    cb && cb(json, dm, view, list, cache);19  }
20})()

其中的 view 就是传入的我们之前创建的 g3d 画布,它上面有个 deserialize 方法,用来反序列化我们的 json 格式的场景文件。可能这个时候大家会发问了,明明之前提到场景文件的是 obj 和 mtl 文件,怎么现在又成了 json 了。不要急,要明白这些我们得先了解一下 HT 的其它基础知识

大家肯定对一些其它框架的设计模式有所了解,像早期 JAVA/Spring 的 mvc ,vue 的 mvvm 等,而 HT 的整体框架类似于 mvp 或 mvvm 模式,采用了统一的 DataModel 数据模型和 SelectionModel 选择模型来驱动所有的 HT 视图组件。HT 官方更愿意把这个模式称之为 ovm 即 Object Vue Mapping。基于这样的设计,用户只需掌握统一的数据接口,就能熟练地使用 HT 了,并不会因为增加了视图组件带来额外的学习成本,这也是为什么 HT 容易上手的原因。

说完这个我们在来谈谈上面 3D 场景文件格式的问题,HT 给我们提供了 ht.JSONSerialize 对象让我们可以对 DataModel 进行 json 格式的序列化和反序列化,而上面的 3D 场景 json 文件就是对我们 3D 模型序列化之后的文件,调用 g3d.deserialize 方法将反序列化的对象加进 DataModel 中,那么我们的画布就会根据传入的 DataModel 绘制出我们的场景了。

那么接下来我们只要重写 Inded3d 类上的 onPostDeserialize 方法,即绘制完场景之后的回调。就能对我们主场景进行基本操作了。

视角转换动画

首先,我们先完成的是三个视角转换的动画

我们直接写在 util 文件当中 ,给它添加一个方法 moveEveAction。方法传入了三个参数,首先是我们的画布 g3d,第二个参数就是我们的视角对象,它记录了每一步转换的初始视角和结束视角。第三个参数是为了衔接每一步视角转换,让其有一个过渡的动画而传入的一个函数 cover

 1 moveEyeAction: function(g3d,moveEyeConfig,cover){
 2  if (!moveEyeConfig) return;
 3    let moveEye = function(obj,time,eas = ‘liner‘){
 4      return new Promise((res,rej) => {
 5                 g3d.setEye(obj.initEye);
 6                 g3d.setCenter(obj.initCenter);
 7                 g3d.moveCamera(obj.moveEye,obj.moveCenter, {
 8                     duration:time,
 9                     easing: function(t){
10                         if(t < 0.5){
11                             cover(t,‘up‘);
12                         }
13                         if (eas === ‘ease-in‘){
14                             return t * t;
15                         }
16                         else if (eas === ‘liner‘){
17                             return t
18                         }
19                         else {
20                             return t
21                         }
22                     },
23                     finishFunc: ()=>{
24                         cover(1,‘down‘);
25                         res(time);
26                     }
27                 });
28             })
29         }
30
31  moveEye(moveEyeConfig[0],moveEyeConfig[0].time,moveEyeConfig[0].eas)
32    .then((res)=>{
33             console.log(1)
34             return moveEye(moveEyeConfig[1],moveEyeConfig[1].time,moveEyeConfig[1].eas)
35    })
36    .then((res)=>{
37             moveEye(moveEyeConfig[2],moveEyeConfig[2].time,moveEyeConfig[2].eas)
38    )}39})

我们在函数中创建了一个方法 moveEye,它创建并返回了一个 promise ,方便我们做回调,防止出现回调地狱的情况。然后我们只要提前先配置好每一步的视角,传入函数中,函数便会依次调用 g3d 上的 moveCamera 方法,在每一步动画结束的时候,调用 cover 函数作为过渡。

我们再来看一下 cover 函数的实现,在 3D 场景初始化时便会调用下方的 create2dCover 方法创建 cover,其实就是在最外层盖上了一层 div ,每一步动画结束的时候,根据传入的参数决定是否变暗完成过渡

 1create2dCover(){
 2  let div = document.createElement("div");
 3  div.style.position = ‘absolute‘;
 4  div.style.background = ‘black‘;
 5  div.style.opacity = 0;
 6  div.style.top = ‘0‘;
 7  div.style.right = ‘0‘;
 8  div.style.bottom = ‘0‘;
 9  div.style.left = ‘0‘;
10  div.style.pointerEvents = ‘none‘;
11  document.body.appendChild(div);
12  let dire = ‘up‘;
13  let cover = function(t,direction,num){
14    if (direction === ‘up‘ && dire === ‘down‘){
15      div.style.opacity = 1- t * 4;
16      if (t > 0.5) dire = ‘up‘;
17     }
18    if (direction === ‘down‘ && dire === ‘up‘){
19      if (t === 1) {
20        div.style.opacity = t;
21        dire = ‘down‘;
22      }
23    }
24  }
25  return cover;
26}

我们再来看一下动画效果

  

第一个视角下的建筑浮现动画

我们先看下 Index3d 类的实现,再加载完场景的时候,我们便会调用上面我们说过的视角转换函数 moveEyeAction , 和我们接下来要讲的城市浮现函数 upCityDemo。

 1 onPostDeserialize(json, dm, view) {
 2  const g3d = this.view;
 3  g3d.setFar(100000);
 4  const nodeUpArr1 = [], nodeUpArr2 = [], nodeUpArr3 = [];
 5  //视角配置参数
 6  const moveEyeConfig = [{
 7    initEye:[-700,390,-974],
 8    initCenter:[-1596,25,-518],
 9    moveEye:[-2572, 390, -974],
10    moveCenter:[-1596,25,-518],
11    time: 9000,
12    eas: ‘ease-in‘
13    },{
14    initEye:[1500,71,900],
15    initCenter:[-1823,25,-636],
16    moveCenter:[-1823,25,-636],
17    moveEye:[-1678, 18, -558],
18    time:8000
19    },{
20    initEye:[2491,600,-1026],
21    initCenter:[0,0,0],
22    moveEye:[-3105, 500, -1577],
23    moveCenter:[-1034, -12, -41],
24    time:8000
25    }]
26  //创建一个蒙板div并返回cover函数
27  let cover = this.create2dCover();
28  //浮现城市的属性初始化
29  dm.each(fnode => {
30  //第一批楼房-市中心
31  if (fnode.getDisplayName() === "up1"){
32    fnode.a(‘startE‘,fnode.getElevation());
33    fnode.setElevation(-200);
34    nodeUpArr1.push(fnode);
35   }
36  //第二批城市-市中心附近建筑
37  if (fnode.getDisplayName() === "up2"){
38    fnode.a(‘startE‘,fnode.getElevation())
39    fnode.setElevation(-100);
40    nodeUpArr2.push(fnode);
41  }
42  //第三批城市-外围建筑
43  if (fnode.getDisplayName() === "up3"){
44    fnode.a(‘startE‘,fnode.getElevation())
45    fnode.setElevation(-100);
46    nodeUpArr3.push(fnode);
47  }
48
49  if(fnode.getDisplayName() === ‘飞光组‘){
50    fnode.eachChild(node => {
51      node.s(‘shape3d.opacity‘,0);
52    })
53  }
54})
55
56  //视角开始变换
57  util.moveEyeAction(g3d,moveEyeConfig,cover)
58  //城市浮现
59  let upCityDemo = function(nodeArr,time,T = 0.6){
60    return new Promise((res,rej)=>{
61    ht.Default.startAnim({
62      duration:time,
63        action: (v,t) => {
64          nodeArr.forEach((node)=>{
65            if(t > T) res(‘已完成‘);
66            let org = node.getElevation();
67            let tar = node.a(‘startE‘);
68            node.setElevation(org + (tar - org) * v)
69          })
70         }
71      })
72    })
73  }
74
75  upCityDemo(nodeUpArr1,11000,0.4).then((res)=>{
76     // console.log(res)
77    return upCityDemo(nodeUpArr2,2000,0.4)
78  }).then((res)=>{
79    return upCityDemo(nodeUpArr3,2000);
80  }).then((res)=>{
81    //城市出现,开始动画
82    //this.startAnimation(g3d,dm);
83  })
84}

首先我们将城市分别分为三批放入不同的数组中,然后类似的,创建了 upcityDemo 并返回了一个 promise,我们只需要调用并传入每批城市节点,它们便会依次执行建筑上升。还有一点要提的是这里动画用的是 HT 提供的动画函数  ht.Default.startAnim 。这里我们简单介绍一下,HT 提供了 Frame-Based 和 Time-Based 两种动画方式,根据是否设置了 frames 和 interval 属性来决定是哪种方式。 第一种方式用户通过指定 frames 动画帧数, 以及 interval 动画帧间隔参数控制动画效果。 第二种 Time-Based 用户只需要指定 duration 的动画周期的毫秒数即可,HT 将在指定的时间周期内完成动画, 值得一提的是不同于   Frame-Based 方式有明确固定的帧数即 action 函数被调用的次数,Time-Based 方式的帧数或 action 函数被调用次数取决于系统环境 (类似于 setinterval 和 requestAnimate 的区别)

我们先看下动画效果,第一步视角下的动画转换我们就算完成了

贯穿全部视角下的动画

我们所有的动画和上面一样通过 ht.Default.startAnim 函数实现,我们只需要将不同的动画函数放入 action 中,并通过控制它们不同的步数就能实现不一样的速度效果。

我们共有五个动画效果,旋转动画可以归为一类

· 建筑下的水波扩散动画

· 风车,建筑底下光圈旋转动画

· 道路偏移动画

· 市中心上方光线流动动画

· 建筑上面的数字飞光动画

 1 ht.Default.startAnim({
 2             frames: Infinity,
 3             interval: 20,
 4             action: () => {
 5                 //扩散水波动画
 6                 waveScale(scaleList,dltScale,maxScale,minScale);
 7                 //风车旋转,建筑底下光圈旋转
 8                 rotationAction(roationFC,dltRoattion);
 9                 rotationAction(roationD,dltRoattionD);
10                 rotationAction(roationD2,-dltRoattionD2);
11                 //道路偏移
12                 uvFlow(roadSmall,dltRoadSmall);
13                 uvFlow(roadMedium,dltRoadMedium);
14                 uvFlow(roadBig,dltRoadBig);
15                 //光亮建筑下的数字飞光
16                 numberArr.forEach((node,index)=>{
17                     blockFloat(node,numFloadDis);
18                 })
19                 //市中心上方亮线的流动
20                 float.eachChild(node => {
21                     let offset = node.s(‘shape3d.uv.offset‘) || [0, 0];
22                     node.s(‘shape3d.uv.offset‘, [offset[0] + 0.05, offset[1]]);
23                 })
24             }
25         });

我们先讲前面四种较为简单动画的实现,像市中心上方亮线的流动动画逻辑简单,我们就直接写在了 action 函数中,每一步控制 x 方向上的贴图偏移即可

其它动画我们都封装为了对应的函数,如下

 1 //道路偏移动画
 2 //定义三种道路的步进
 3 const dltRoadSmall = 0.007, dltRoadMedium = 0.009, dltRoadBig = 0.01;
 4 //获取三种道路节点
 5 let roadSmall = dm.getDataByTag(‘roadSmall‘);
 6 let roadMedium = dm.getDataByTag(‘roadMedium‘);
 7 let roadBig = dm.getDataByTag(‘roadBig‘);
 8 let float = dm.getDataByTag(‘float‘);
 9 //定义偏移动画函数
10 let uvFlow = function(obj,dlt){
11     let offset = obj.s(‘all.uv.offset‘) || [0, 0];
12     obj.s(‘all.uv.offset‘, [offset[0] + dlt, offset[1]]);
13 }
14
15 //水波缩放动画
16 //定义扩大范围和每步扩大速度
17 const maxScale = 1.5, dltScale = 0.06;
18 //获取缩放节点
19 let scaleList = dm.getDataByTag(‘scale‘);
20 //定义缩放函数
21 let waveScale = function(obj, dlt, max, min){
22     obj.eachChild(node => {
23         // 扩散半径增加
24         if (!node.a(‘max‘)) node.a(‘max‘, node.getScaleX() + max);
25         if (!node.s(‘shape3d.opacity‘)) node.s(‘shape3d.opacity‘,1);
26         let s = node.getScaleX() + dlt;
27         let y = node.getScale3d()[1]
28         let opa = node.s(‘shape3d.opacity‘) - 0.02;
29         // 扩散半径大于最大值的时候,重置为最小值,透明度设为1
30         if (s >= node.a(‘max‘)){
31             opa = 1;
32             s = 0;
33         }
34         // 设置x,y,z方向的缩放值
35         node.s(‘shape3d.opacity‘,opa)
36         node.setScale3d(s, y, s);
37         });
38 }
39 //旋转图元
40 //定义三种不同旋转图元数组和旋转速度
41 const roationFC = [], roationD = [], roationD2 = [], dltRoattionD = Math.PI / 90, dltRoattionD2 = Math.PI / 60, dltRoattion = Math.PI / 30;
42 //获取所有旋转图元并分别放入数组中
43 let roationFCDatas = dm.getDataByTag(‘roationFC‘);
44 let roationdDatas = dm.getDataByTag(‘di‘);
45 roationFCDatas.eachChild(node =>{
46     node.eachChild(node => {
47         if (node.getDisplayName() === ‘风机叶片‘){
48             roationFC.push(node);
49         }
50     })
51 });
52 roationdDatas.eachChild(node => {
53     if (node.getDisplayName() === ‘底‘){
54         roationD.push(node)
55     }
56     if (node.getDisplayName() === ‘底2‘){
57         roationD2.push(node)
58     }
59 });
60 //定义旋转函数
61 let rotationAction = function(obj,dlt){
62     obj.forEach(node => {
63         if (node.getDisplayName() === ‘风机叶片‘){
64             //获得当前旋转角度
65             let rotationZ = node.getRotation3d()[2];
66             //每步增加dlt
67             node.setRotation3d([0,0,rotationZ + dlt]);
68         }
69         if (node.getDisplayName() === ‘底‘ || node.getDisplayName() === ‘底2‘){
70             //获得当前旋转角度
71             let rotationY = node.getRotation3d()[1];
72             //每步增加dlt
73             node.setRotation3d([0,rotationY + dlt,0]);
74         }
75     })
76 }

写完之后我们再看一下动画效果

最后就是我们的稍微繁琐一点的数字飞光动画了。每座城市上方都有不同的六条飞光,我们需要每次都是随机出现两条,并且每条的速度都是不一样的。和之前的动画一样的,我们先获取所有的飞光节点并分类好,如下

 1 //数字浮动
 2 let numberArr, numFloadDis = 15, numFloatDlt = 0.07;
 3 numberArr = new Array(28);
 4 for (let i = 0;i < 28; i++){
 5     numberArr[i] = new Array(6)
 6 }
 7 //产生两个随机数,并以数组形式返回
 8 let randerdom2 = function(){
 9     let num1 = Math.floor(Math.random() * 3);
10     let num2 = Math.floor((Math.random() * 3 + 3));
11     return [num1,num2];
12 }
13 //将所有的浮动数字按城市分组添加进数组
14 let i = 0,j=0;
15 dm.each(node => {
16     if (node.getDisplayName() === ‘飞光组‘){
17         node.eachChild(node => {
18             node.s(‘shape3d.opacity‘,0);
19             node.setElevation(0);
20             numberArr[i][j++] = node;
21         })
22         j=0;
23         i++;
24     }
25 });
26 //属性初始化
27 let initArrAtr = function(){
28     for (let i = 0; i < numberArr.length; i++){
29         for (let j = 0; j < numberArr[i].length; j++){
30             //每条数字的随机数度
31             numberArr[i][j].a(‘randomSpeed‘, (numFloatDlt * 100 + Math.floor(Math.random() * 5))/100);
32             //控制每条数字是否停止上升
33             numberArr[i][j].a(‘stop‘,false);
34             //每栋楼上的已升起的飞光数量
35             numberArr[i].comNum = 0;
36             //每栋楼层当前的两条飞光
37             numberArr[i].one = randerdom2()[0];
38             numberArr[i].two = randerdom2()[1];
39         }
40     }
41 }
42 initArrAtr();
43 //重置单楼属性
44 let czArr = function(singleRoom){
45         //每栋楼上的已升起的数量
46         singleRoom.comNum = 0;
47         //重新随机设置每栋楼层出现的两条飞光
48         singleRoom.one = randerdom2()[0];
49         singleRoom.two = randerdom2()[1];
50         //设置飞光的随机速度
51         singleRoom.forEach((node, index)=>{
52             node.a(‘stop‘,false);
53             node.a(‘randomSpeed‘, (numFloatDlt * 100 + Math.floor(Math.random() * 5))/100);
54         })
55 }

当初始属性都设置完成后就该定义我们的动画函数了

 1 let blockFloat = function(obj, dis){
 2     //获取当前建筑
 3     let allNumArr = obj;
 4     //获取当前建筑出现的两条飞光
 5     let floatArr = [allNumArr[allNumArr.one],allNumArr[allNumArr.two]];
 6     let lth = floatArr.length;
 7     //遍历并控制这两条飞光及动画
 8     for (let j = 0; j < lth; j++){
 9         let node = floatArr[j];
10         //如果当前飞光已停则停止此条飞光下一步动画
11         if (node.a(‘stop‘)) continue;
12         //获得当前飞光初始高度如果没有则手动设置当前为初始高度
13         let startE = node.a(‘startE‘);
14         if (startE == null) node.a(‘startE‘, startE = node.getElevation());
15         // 获得当前飞光速度和透明度值
16         let dlt = node.a(‘randomSpeed‘);
17         let float = node.a(‘float‘) || 0;
18         let opa = node.s(‘shape3d.opacity‘) || 0,
19             opaDlt = 0.01;
20
21         node.setElevation(startE + dis * float);
22         //上升的高度到达一定值设置透明度为1
23         if (float > 8){
24             node.s(‘shape3d.opacity‘,1)
25             opaDlt = -0.02
26         }
27         //上升的高度到达最高则让当前建筑飞光到达数量加一,并停止进一步上升
28         if (float > 12){
29             allNumArr.comNum ++;
30             node.a(‘stop‘,true);
31             node.a(‘float‘, 0);
32             node.setElevation(startE);
33             node.s(‘shape3d.opacity‘,0);
34             //当前建筑飞光到达数量到达两条,重置建筑上所有飞光属性
35             if (allNumArr.comNum === 2){
36                 czArr(allNumArr);
37             }
38             continue;
39         }
40         float += dlt;
41         opa += opaDlt;
42         node.s(‘shape3d.opacity‘,opa)
43         node.a(‘float‘, float);
44     }
45 }

我们看下效果

到这,我们所有的动画就已经写完了。还等什么呢,一起来创建一个属于你自己心中理想的智能化城市吧

(ps: 不仅如此,HT官网中 还包含了数百个工业互联网 2D 3D 可视化应用案例,点击这里体验把玩:http://www.hightopo.com/demos/index.html)

原文地址:https://www.cnblogs.com/htdaydayup/p/12067979.html

时间: 2024-10-11 01:11:13

基于 HTML5 WebGL 构建智能数字化城市 3D 全景的相关文章

基于 HTML5 WebGL 的智慧城市(一)

前言 中共中央.国务院在今年12月印发了<长江三角洲区域一体化发展规划纲要>(下文简称<纲要>),并发出通知,要求各地区各部门结合实际认真贯彻落实. <纲要>强调,要提升基础设施互联互通水平,打造数字长三角,协同建设新一代信息基础设施,共同推动重点领域智慧应用.大力发展基于物联网.大数据.人工智能的专业化服务,提升各领域融合发展.信息化协同和精细化管理水平.围绕城市公共管理.公共服务.公共安全等领域,支持有条件的城市建设基于人工智能和 5G 物联的城市大脑集群. 城市治

基于HTML5 WebGL的工业化3D电子围栏

前言 现代工业化的推进在极大加速现代化进程的同时也带来的相应的安全隐患,在传统的可视化监控领域,一般都是基于 Web SCADA 的前端技术来实现 2D 可视化监控,本系统采用 Hightopo 的 HT for Web 产品来构造轻量化的 3D 可视化场景,该 3D 场景从正面展示了一个现代化工厂的现实场景,包括工厂工人的实时位置.电子围栏的范围.现场的安全情况等等,帮助我们直观的了解当前工厂人员的安全状况. 本篇文章通过对工厂可视化场景的搭建和模型的加载,人物实时定位代码的实现.电子围栏和轨

基于 HTML5 + WebGL 实现 3D 可视化地铁系统

前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCADA 的前端技术来实现 2D 可视化监控,本系统采用 Hightopo 的 HT for Web 产品来构造轻量化的 3D 可视化场景,该 3D 场景从正面展示了一个地铁站的现实场景,包括地铁的实时运行情况,地铁上下行情况,视频监控,烟雾报警,电梯运行情况等等,帮助我们直观的了解当前的地铁站. 系统中

基于 HTML5 Canvas 的智能安防 SCADA 巡逻模块

基于 HTML5 Canvas 的智能安防 SCADA 巡逻模块 前言 随着大数据时代的来临,物联网的日益发展,原先的 SCADA 系统本身也在求新求变,从最开始的专业计算机和操作系统,到通用计算机和相关软件,再到现在基于 HTML5 Canvas 的新型组态开发,其应用的范围也从最初的电力,逐渐发展到应用于电力.冶金.化工.自动化群控等等大部分工控场景中,本文是将传统安防结合 SCADA 系统,制作的智能安防中的巡逻监控模块,主要采用 HT for Web 作为开发环境. 代码实现 绘制背景色

基于HTML5实现的Heatmap热图3D应用

Heatmap热图通过众多数据点信息,汇聚成直观可视化颜色效果,热图已广泛被应用于气象预报.医疗成像.机房温度监控等行业,甚至应用于竞技体育领域的数据分析. 已有众多文章分享了生成Heatmap热图原理,可参考<How to make heat maps>和<How to make heat maps in Flex>,本文将介绍基于HTML5技术的实现方式,主要基于Cavans和WebGL这两种HTML5的2D和3D技术的应用,先上最终例子实现的界面效果和操作视频: 实现Heat

基于 HTML5 WebGL 的 水泥工厂可视化系统

前言 如今的制造行业,基于数据进行生产策略制定与管理已经成为一种趋势,特别是 工业4.0 的浪潮下,数据战略已经成为很多制造企业的优先战略,而数据可视化以更直观的方式,帮助指导决策,成为数据分析传递信息的重要工具.通过数据可视化系统助力实现数据驱动的工业世界,为 工业4.0 提供更加灵活.敏捷.高效.个性化的数据支撑.今天就给大家带来一个采用 Hightopo 的 HT for Web 产品实现了一个水泥工厂可视化系统. 系统预览 本案例共有七个子系统: 数据概况 -- 展示全厂年月时间单位的各

基于 HTML5 WebGL 的 3D 智慧隧道漫游巡检

前言 这次为大家展示的是通过 HT for Web 灵活的图型化编辑工具打造的智慧隧道监控系统.通过 HTML5 技术实现了桌面和移动端的跨平台性,同时现实了可视化运维. 这次主要跟大家分享里面的漫游巡检功能,完美进行第一人称视角体验整体结构环境,酷似游戏一样给人一种真实的感受,比平面更加直观,随意游离与虚拟和现实之间. 代码实现 整个场景是由 3D 组件搭建而成的,需要大量的代码,为了简化,我用 HT 封装的 ht.JSONSerializer 来将场景序列化为一个 json 文件.在代码中,

基于 HTML5 WebGL 的 3D 网络拓扑图

在数据量很大的2D 场景下,要找到具体的模型比较困难,并且只能显示出模型的的某一部分,显示也不够直观,这种时候能快速搭建出 3D 场景就有很大需求了.但是搭建 3D 应用场景又依赖于通过 3ds Max 或 Maya 的专业 3D 设计师来建模,Unity 3D 引擎做图形渲染等,这对用户来说都是挑战!不过,HT 一站式的提供了从建模到渲染,包括和 2D 组件呈现和数据融合的一站式解决方案.HT 基于 WebGL 的 3D 技术的图形组件 ht.graph3dView 组件通过对 WebGL 底

基于HTML5 WebGL实现3D飞机叶轮旋转

在上一篇<基于HT for Web矢量实现2D叶轮旋转>中讲述了叶轮旋转在2D拓扑上的应用,今天我们就来讲讲叶轮旋转在3D上的应用. 在3D拓扑上可以创建各种各样的图元,在HT for Web系统中提供了一些常规的3D模型,但是对于那些比较复杂的模型,比如汽车.人物等模型就无能为力了,那再项目中需要用到这样的模型该肿么办呢?这时候就需要借助专业的3ds Max工具来建模了,然后通过3ds Max工具将模型导出成obj文件,然后再项目中引用导出的obj文件,这样就能成功的使用上复杂的图元了. 在