基于HTML5 Canvas的工控SCADA模拟飞机飞行

昨天看到一篇文章说是学习如何开飞机的,然后我就想,如果我也可以开飞机那就好玩了,每个人小时候都想做飞行员!中国飞行员太难当了,再说也不轻易让你开飞机!后来我就想如果能用 HT 开飞机那就是真的有趣了,哈哈,这个实现的效果还是很不错的,可以让你满足一下开飞机的虚荣心

Demo 地址: http://hightopo.com/guide/guide/plugin/obj/examples/example_path.html

来看下具体实现的效果:

这个例子基本上完全模拟了飞机的飞行模式,包括起飞跑道,包括飞机的移动路径,螺旋桨的旋转,机尾的指示灯等部分。

首先,最重要的是我们的飞机模型,前面有文章写到过,HT 内部封装了一个方法 ht.Default.loadObj 来加载 OBJ 文件:

 1 ht.Default.loadObj('obj/plane.obj', 'obj/plane.mtl', {                    
 2     center: true,
 3     r3: [0, -Math.PI/2, 0], // make plane face right
 4     s3: [0.15, 0.15, 0.15], // make plane smaller
 5     finishFunc: function(modelMap, array, rawS3){
 6         if(modelMap){                            
 7             modelMap.propeller.r3 = {//propeller 螺旋桨
 8             func: function(data){
 9                 return [data.a('angle'), 0, 0]; 
10             }
11         };                             
12         // make propeller a litter bigger
13         modelMap.propeller.s3 = [1, 1.2, 1.2]; 
14         modelMap.propeller.color = 'yellow';
15     } 
16 });

这里面的 modelMap.propeller 是 OBJ 文件中定义好的 modelMap 对象中的 propeller 对象,你可以试着打印 modelMap 看看输出结果。

这个方法里的 finishFunc(modelMap, array, rawS3) 用于加载后的回调处理,具体查阅 HT for Web OBJ 手册,我们还添加了一个在 OBJ 模型中没有的飞机尾部的“红色闪烁指示灯”,这里用到的是组合模型 array(所有材质组成的数组,里面有至少一个模型),我们在 array中加入一个新的球模型:

 1 // add a sphere model as an indicator light 指示灯
 2 array.push({
 3     shape3d: ht.Default.createSmoothSphereModel(),
 4     t3: [-40, 10, 0],
 5     s3: [6, 6, 6],
 6     color: {
 7         func: function(data){
 8             return data.a('light') ? 'red': 'black';
 9         }
10     }
11 });

这里的 shape3d 是 HT 封装的一个属性名,通过 setShape3dModel(name, model) 函数注册的或者是通过 getShape3dModel(name) 函数返回的注册过的 3D 模型,如何注册 3D 模型可查阅 HT for Web 建模手册

color 属性名对应了一个对象,这边的定义是这样的,color 直接通过 data.getAttr('a') 获取 data.setAttr(‘a’, value) 中的值,这样做有两个好处,一是可以不污染 HT 的常用属性操作,所以 HT 专门定义了这个 attr 属性类型,是 HT 预留给用户存储业务数据的;二是这样也很方便数据绑定,我们可以通过在需要更改属性的地方调用 setAttr 方法,非常方便。

接着我们通过 ht.Default.setShape3dModel(name, model) 来将我们刚刚组合好的模型 array 注册成我们要的“plane”模型:

1 ht.Default.setShape3dModel('plane', array);

注册好模型后肯定是要调用这个模型,我们可以通过 shape3d 属性来调用这个模型,并且在这个模型中自定义上面代码中出现过的 light 属性和 angle 属性:

 1 plane = new ht.Node();
 2 plane.s3(200, 200, 200);
 3 plane.s3(rawS3);
 4 plane.s({
 5     'shape3d': 'plane',
 6     'shape3d.scaleable': false,
 7     'wf.visible': true,//线框是否可见
 8     'wf.color': 'white',
 9     'wf.short': true //是否显示封闭的线框,true为不封闭的短线框
10 });
11 plane.a({
12     'angle': 0,
13     'light': false
14 });

因为飞机还有螺旋桨、指示灯两个功能,我们还得对这两个模型做动画效果,可查阅 HT for Web 动画手册,通过用户在 form 表单上选择的结果来决定飞机飞行持续时间、看飞机的视角、飞机沿着“航线”飞行所要旋转的角度、机尾指示灯的“闪烁”功能等等,最后别忘了飞机停止飞行时,如果要让飞机继续飞行,就得回调这个动画,并且设置灯不再闪烁,别忘了要启动动画:

 1 params = {
 2     delay: 1500,
 3     duration: 20000,
 4     easing: function(t){ 
 5         return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2));
 6     },
 7     action: function(v, t){
 8         var length = g3d.getLineLength(polyline),
 9         offset = g3d.getLineOffset(polyline, length*v),
10         point = offset.point,
11         px = point.x,
12         py = point.y,
13         pz = point.z,
14         tangent = offset.tangent,
15         tx = tangent.x,
16         ty = tangent.y,
17         tz = tangent.z;
18         plane.p3(px, py, pz);
19         plane.lookAt([px + tx, py + ty, pz + tz], 'right');
20                         
21         var camera = formPane.v('Camera');
22         if(camera === 'Look At'){
23             g3d.setCenter(px, py, pz);
24         }
25         else if(camera === 'First Person'){                            
26             g3d.setEye(px - tx * 400, py - ty * 400 + 30, pz - tz * 400);
27             g3d.setCenter(px, py, pz);                            
28         }
29                         
30         plane.a('angle', v*Math.PI*120);                        
31         if(this.duration * t % 1000 > 500){
32             plane.a('light', false);
33         }else{
34             plane.a('light', true);
35         }                        
36     },
37     finishFunc: function(){
38         animation = ht.Default.startAnim(params);
39         plane.a('light', false);
40     }                  
41 };                               
42                 
43 animation = ht.Default.startAnim(params);

其实最让我们好奇的是描绘的路径跟飞机本身的飞行并没有关系,还有那么多左拐右拐的,要如何做才能做到呢?

接下来我们来描绘路径,首先这个路径是由 ht.Polyline 作为基础来描绘的:

 1 polyline = new ht.Polyline();   
 2 polyline.setThickness(2);
 3 polyline.s({
 4     'shape.border.pattern': [16, 16],
 5     'shape.border.color': 'red',
 6     'shape.border.gradient.color': 'yellow',
 7     'shape3d.resolution': 300,
 8     '3d.selectable': false
 9 });
10 dataModel.add(polyline);

上面的代码只是向 datamodel 数据模型中添加了一个 polyline 管线而已,不会显示任何东西,要显示“航道”首先就要设置航道所在的点,我们先设置航道的初始点:

1 points = [{ x: 0, y: 0, e: 0 }];
2 segments = [1];

这个 points 和 segments 是 HT for Web Shape 手册中定义的,points 是 ht.List 类型数组的定点信息,顶点为 { x: 100, y: 200 } 格式的对象;segments 是 ht.List 类型的线段数组信息,代表 points 数组中的顶点按数组顺序的连接方式。

图中“航道”左侧的多个圆形轨道也是通过设置 points 和 segments 来设置的:

1 for(var k=0; k<count+1; k++){
2     var angle = k * Math.PI * 2 * round / count;
3     points.push({
4         x: cx + radius * Math.cos(angle),
5         y: cy + radius * Math.sin(angle),
6         e: k * height / count
7     }); 
8     segments.push(2);
9 }

接下来几个拐点也是这种方法来实现的,这里就不赘述了,如果你还没看手册的话,这里标明一点,segments 只能取值 1~5,1 代表一个新路径的起点;2 代表从上次最后点连接到该点;3 占用两个点信息,第一个点作为曲线控制点,第二个点作为曲线结束点;4 占用3个点信息,第一和第二个点作为曲线控制点,第三个点作为曲线结束点;5 不占用点信息,代表本次绘制路径结束,并闭合到路径的起始点:

 1 points.push({ x: cx+radius, y: 0, e: height/2 });
 2 points.push({ x: 0, y: 0, e: height/2 });
 3 segments.push(3);
 4      
 5 points.push({ x: radius, y: -radius, e: height/2*0.7 });
 6 points.push({ x: radius*2, y: radius, e: height/2*0.3 });
 7 points.push({ x: radius*3, y: 0, e: 0 });
 8 segments.push(4);   
 9 
10 points.push({ x: 0, y: 0, e: 0 });
11 segments.push(2);  

我们已经把路径上的点都添加进“航道”中了,接下来要把点都设置到管道上去才会显示在界面上:

1 polyline.setPoints(points);
2 polyline.setSegments(segments);  

“跑道”就比较简单了,只是一个 Node 节点然后设置基础效果而已,没什么特别的:

 1 runway = new ht.Node();
 2 runway.s3(-cx+radius*3, 1, 200);
 3 runway.p3(cx+runway.getWidth()/2, -22, 0);
 4 runway.s({
 5     'all.color': '#FAFAFA',
 6     'all.transparent': true,
 7     'all.reverse.cull': true,
 8     'all.opacity': 0.8,
 9     '3d.selectable': false
10 });
11 dataModel.add(runway);

最后,在界面上添加一个 formPane 表单面板,定义好之后可以直接添加到 body 上,这样就不会跟 graph3dView 有显示的联系了。

formPane 可以用 formPane.addRow 方法动态添加行,这个方法中可以直接对动态变化的数据进行交互,例如本例中的是否有动画 Animation,我们利用 checkBox 来记录选中或者非选中的状态:

 1 {
 2     checkBox: {
 3         label: 'Animation',
 4         selected: true,
 5         onValueChanged: function(){
 6             if(this.isSelected()){
 7                 animation.resume();
 8             }else{
 9                 animation.pause();
10             }                               
11         }
12     }
13 }

也可以通过设置“id”来记录动态改变的值,然后 formPane就会通过调用 formPane.v(id) 来获取当前值。

至此,整个 Demo 的解释到此为止,如果还有不懂的可以先查阅我们官网 HT for Web,之后还有不懂的可以私信我,但还是希望你们能仔细阅读,不然很浪费我们双方的时间,谢谢~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

sizeHint() minimumSizeHint() sizePolicy() 的使用

http://blog.csdn.net/zzwdkxx/article/details/32116999

321
来自专栏Coding迪斯尼

VUE+WebPack前端游戏设计:能量气泡的螺旋升腾特效

1043
来自专栏点滴积累

geotrellis使用(九)使用geotrellis进行栅格渲染

目录 前言 图像渲染 总结 参考链接 一、前言        前面几篇文章讲解了如何使用Geotrellis进行数据处理、瓦片生成等,今天主要表一下如何使用Ge...

3085
来自专栏华章科技

升值加薪Excel神助攻,数据透视表堪称神器!

VLOOKUP、数据透视表、条件格式…你用这几个技巧做,80%的工作需求都能解决。今天特意整理了这些操作技巧,拯救同在“表海”中挣扎的你,让你的工作效率超乎想...

532
来自专栏每日一篇技术文章

SceneKit_入门13_骨骼动画

1.创建工程(略) 2.加载场景文件(略) 3.添加框架SceneKit/Scenekit.h 4.创建场景资源对象

762
来自专栏yang0range

Android硬件加速原理和简介

原理:使用PNG图片(BitmaoDrable)解码PNG图片生成Bitmap,传到底层,有GPU渲染图片解码,消耗CPU运算资源,Bitmap占内存大,绘制慢...

744
来自专栏进步博客

[温故知新] Text-level semantics

The em element represents stress emphasis of its contents.

973
来自专栏hightopo

原 荐 基于HTML5 Canvas的工控S

913
来自专栏向治洪

Android开发之Path详解

在制作高级控件的时候往往会用到很多的高级数学公式,例如本文将要讲到的贝塞尔曲线,结合Path使用,可以实现很多复杂的动画效果。 一.Path常用方法表 作...

2935
来自专栏腾讯Bugly的专栏

Android自绘动画实现与优化实战——以Tencent OS录音机波形动画为实例

前言 我们所熟知的,Android 的图形绘制主要是基于 View 这个类实现。 每个 View 的绘制都需要经过 onMeasure、onLayout、onD...

3034

扫码关注云+社区