基于 HTML5 WebGL 的 3D 科幻风机
<div class="para"><span data-mce-=""><span data-mce-=""><strong>前言</strong></span></span></div><div class="para"> 许多世纪以来,风力机同水力机械一样,作为动力源替代人力、畜力,对生产力的发展发挥过重要作用。近代机电动力的广泛应用以及二十世纪50年代中东油田的发现,使风机发电机的发展缓慢下来。</div>
<div class="para">70年代初期,由于“石油危机”,出现了能源紧张的问题,人们认识到常规矿物能源供应的不稳定性和有限性,于是寻求清洁的可再生能源遂成为现代世界的一个重要课题。风能作为可再生的、无污染的自然能源又重新引起了人们重视。</div>
<div class="para">现在我希望可以通过这个风机 demo 使风力发电机的各个功能近距离的展示给大家,使大家能更了解风力发电机。</div>
<div class="para">本 demo 使用 HT for Web 产品轻量化 HTML5/WebGL 建模的方案。</div>
<div class="para">demo 链接:https://hightopo.com/demo/fan3d-magic/</div>
<div class="para"> </div>
<div class="para"> </div>
<div class="para"><span data-mce-=""><strong>风机主要功能介绍</strong></span></div>
<div class="para"><strong><span data-mce-="">效果:</span></strong></div>
<div class="para"><strong><strong><span data-mce-=""> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210085005345-548668797.gif"></span></strong></strong></div>
<div class="para"> </div>
<div class="para"><span data-mce-=""><strong>周围环境功能:</strong></span></div>
<div class="para">
<div class="para">
<div class="para"><strong> 一. 风速值。</strong></div>
<div class="para"> 虚拟一个风速值,风速值会影响风机的发电效率和变桨系统的变化。</div>
<div class="para"> </div>
<div class="para"> <strong>二. 数据统计<strong>。</strong></strong></div>
<div class="para"> <strong><strong><img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191209172321468-689024577.png"></strong></strong></div>
<div class="para">
<p> 环境温度、机舱温度、齿轮箱温度、风速的图形百分比会随着时间发生改变。</p>
</div>
<div class="para"> <strong>三. 环境监测<strong>。</strong></strong></div>
<div class="para"> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191209172728465-1573602002.png"></div>
<div class="para"> 左边对风机的各个参数实施了监测,右面是风速变化的折线图。</div>
<div class="para"> </div>
<div class="para"> <strong>四. 数据监测<strong>。</strong></strong> </div>
<div class="para"> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191209172434557-1029332804.png">
<p> 风机在发电的过程中发生的异常情况,发生的故障部位及故障发生的时间。异常信息的收集有利于人们进行异常分析以及异常处理。</p>
</div>
<div class="para"><strong> 五. 偏航系统<strong>。</strong></strong></div>
<div class="para"> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191209173325637-1012604247.png"></div>
<div class="para"> 偏航系统,又称对风装置,是风力发电机机舱的一部分,其作用在于当风速矢量的方向变化时,能够快速平稳地对准风向,以便风轮获得最大的风能。</div>
<div class="para"> </div>
<div class="para"> <strong>六</strong><strong>. 变桨系统<strong>。</strong></strong></div>
<div class="para"> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191209173601648-812764960.png">
<div class="para"> 变桨系统作为大型风电机组控制系统的核心部分之一,对机组安全、稳定、高效的运行具有十分重要的作用。稳定的变桨控制已成为当前大型风力发电机组控制技术研究的热点和难点之一。</div>
<div class="para"> 变桨控制技术简单来说,就是通过调节桨叶的节距角,改变气流对桨叶的攻角,进而控制风轮捕获的气动转矩和气动功率。</div>
<div class="para"> </div>
</div>
<div class="para"><span data-mce-=""><strong>按钮控制功能:</strong></span></div>
<div class="para"> </div>
<div class="para"> <strong>风机启停:<strong><br></strong></strong></div>
<div class="para">
<p> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191209164721868-389185312.gif"></p>
<div class="para">
<div class="para">
<div class="para">
<div class="para"><strong> 线框模式:<strong><br></strong></strong></div>
<div class="para">
<p> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210090414158-1403954501.gif"></p>
</div>
</div>
<div class="para">
<div class="para"><strong> 机舱视角:<strong><br></strong></strong></div>
<div class="para">
<p> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210090500352-1408733444.gif"></p>
</div>
<div class="para">
<div class="para"><strong> 发电工艺:<strong><br></strong></strong></div>
<div class="para">
<p> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210090552310-1881528564.gif"></p>
<p> </p>
</div>
</div>
</div>
</div>
</div>
<p><span data-mce-=""><strong>整理思路:</strong></span></p>
<p> <strong>场景部分</strong>:</p>
<p> 这里把 3D 当做背景嵌套在 2D 场景中。</p>
<p> 这样在初始化图纸的时候,直接反序列化 2D 图纸即可。</p>
<p> </p>
<p> <strong>事件部分</strong>:</p>
<p> 2D 图纸中有很多的按钮,通过它们来控制 3D 中的一些动画。</p>
<p> 实现思路是在反序列化图纸的时候把 2D、3D 的 模型和视图对象挂载到 <span data-mce-=""><strong>window</strong> 上,这样在不同的场景中都可以获取到相应的数据模型。</span></p>
<p> </p>
<p> <strong>环境部分:</strong></p>
<p> 风速、风向、变桨角度这些会在 2D、3D 中所表现,所以可以把他们放到数据池里面,方便管理。</p>
<p> </p>
<p><span data-mce-=""><strong>具体代码实现:</strong></span></p>
<p><strong>场景搭建:</strong>上面说了,我们把 3D 当做背景嵌套在 2D 中,所以只需要序列化 2D 即可,里面需要进行背景判断的部分代码。</p>
<p><strong>相关伪代码</strong><strong>:</strong></p>
<div class="cnblogs_code" data-mce-="">
<pre>graphView.deserialize('displays/demo/风力发电机/风力发电机结构查看.json', function (json, dm, gv, datas) {</pre>
<pre> if (json.title) document.title = json.title
if (json.a['json.background']) {
var bgJSON = json.a['json.background']
if (bgJSON.indexOf('displays') === 0) {
var bgGv = new ht.graph.GraphView()
bgGv.deserialize(bgJSON)
bgGv.addToDOM()
graphView.addToDOM(bgGv.getView())
}
else if (bgJSON.indexOf('scenes') === 0) {
var bgG3d = new ht.graph3d.Graph3dView()
bgG3d.deserialize(bgJSON)
bgG3d.addToDOM()
graphView.addToDOM(bgG3d.getView())
}
graphView.handleScroll = function () { }
}</pre>
<pre> })</pre>
</div>
<p> </p>
<p><strong>模拟风速:</strong>每隔30s,随机产生一个值,当做风速值。</p>
<p><strong>相关伪代码</strong><strong>:</strong></p>
<div class="cnblogs_code">
<pre><span data-mce-="">// <span data-mce-="">模拟风速<span data-mce-="">
mockWindSpeed() {
<span data-mce-="">return 8 + Math.random() * 12<span data-mce-="">
}</span></span></span></span></span></pre>
</div>
<p> </p>
<p><strong>数据统计:</strong>每隔30s,随机变换。</p>
<p>效果:</p>
<p><img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210100137990-478649784.gif"></p>
<p><strong>相关伪代码</strong><strong>:</strong></p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 指针和扇叶旋转的角度 进行变化
var oldPointerValue = pitchSystem.a('pointer') || 0<br>
<span data-mce-="">//<span data-mce-=""> 风机扇叶和变桨系统的旋转角度
var newRotateAngular = (<span data-mce-="">this.windSpeed - 8) * 7.5 *<span data-mce-=""> translateAngularRadian.radian
var addPointerValue = newRotateAngular -<span data-mce-=""> oldPointerValue
var oldWindSpeedClip = environmentalData.a('windSpeedClip') || 0<span data-mce-="">
var newWindSpeedClip = (<span data-mce-="">this.windSpeed - 8) / 12<span data-mce-="">
var addWindSpeedClip = newWindSpeedClip -<span data-mce-=""> oldWindSpeedClip
var anim =<span data-mce-=""> {
duration: 1e3<span data-mce-="">,
easing: (v) =><span data-mce-=""> {
<span data-mce-=""> return v *<span data-mce-=""> v
},
action: (v) =><span data-mce-=""> {
var windSpeed = Number(<span data-mce-="">this.windSpeed.toFixed(2<span data-mce-="">))
var Max = Number(MaxValue.toFixed(2<span data-mce-="">))
var average = Number(Aver.toFixed(2<span data-mce-="">))
var windSpeedClip = oldWindSpeedClip + (addWindSpeedClip *<span data-mce-=""> v)
<span data-mce-=""> //<span data-mce-=""> 设置发电参数随机数据
<span data-mce-=""> generator.a({ windSpeed })
<span data-mce-="">//<span data-mce-=""> 设置环境监测随机数据
<span data-mce-=""> environmentalData.a({ windSpeed, windSpeedClip })
<span data-mce-=""> //<span data-mce-=""> 设置统计参数随机数据
<span data-mce-=""> statisticalParam.a({ average, Max, windSpeed })
<span data-mce-=""> //<span data-mce-=""> 设置变航系统的指针角度
pitchSystem.a('pointer', oldPointerValue + (addPointerValue *<span data-mce-=""> v))
}
}<br>
ht.Default.startAnim(anim)</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>这里涉及到角度和弧度的转换。<span data-mce-=""><strong>1° = Math.PI / 180°,1rad = 180° / Math.PI</strong>,因为场景中使用的是弧度制,所以需要把随机出的角度值转换成弧度。</span></p>
<p>这里解释下代码,先获取到当前的值。然后在加上 随机值 - 当前值。比如当前值为 16,随机出的数值有两种情况,1:比当前值大。2:比当前值小。</p>
<p>如果比当前值大的话,比如18,那么就是这样 16 + (18 - 16) * v ( easing 函数运算后的值)</p>
<p>如果比当前值小的话,比如13,那么就是这样 16 + (13 - 16) * v ( easing 函数运算后的值)</p>
<p>这样当随机的时候,就会从当前值平滑的改变到目标值。</p>
<p> </p>
<p><strong>数据统计</strong>:每隔30s,监测当前风机的故障信息。</p>
<p>效果: </p>
<p><img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210101459490-177046093.gif"></p>
<p> 这里使用了 <span data-mce-=""><strong>table.json</strong> 文件,通过修改 <span data-mce-=""><strong>ht.dataSource </strong><span data-mce-="">属性添加实时信息。</span></span></span></p>
<p> <strong>相关伪代码</strong><strong>:</strong></p>
<div class="cnblogs_code">
<pre>var checkInternals = () =><span data-mce-=""> {
<span data-mce-="">/*<span data-mce-="">*
* 故障信息
* 变桨系统 主轴 偏航系统 齿轮箱 油冷装置 发电机 风冷装置
<span data-mce-="">*/<span data-mce-="">
var FailureStatus =<span data-mce-=""> {
<span data-mce-="">//<span data-mce-=""> 正常状态
status1: <span data-mce-="">new<span data-mce-=""> Map([
],
],
],
],
],
],
]
]),
<span data-mce-="">//<span data-mce-=""> 异常状态
status2: <span data-mce-="">new<span data-mce-=""> Map([
],
],
],
],
],
],
]
]),
}
<span data-mce-="">//<span data-mce-=""> 返回设备正常的状况 status 1: 正常 2: 不正常
var mockQquipmentFailure = (status) =><span data-mce-=""> {
var { rangeRandom } =<span data-mce-=""> common
var index = rangeRandom(7<span data-mce-="">)
<span data-mce-="">//<span data-mce-=""> 返回随机出来的设备情况
<span data-mce-="">return<span data-mce-=""> FailureStatus[`status${status}`].get(index)
}
var info = randomInfo
var targetTag = randomInfo
<span data-mce-="">this.tableArr = table.a('ht.dataSource'<span data-mce-="">)
var currentTimeFormat = DateUtil.formatHourTime(<span data-mce-="">new<span data-mce-=""> Date())
<span data-mce-="">//<span data-mce-=""> 默认是正常 如果找到故障关键字的话 赋值为 异常
var status = 'normal'<span data-mce-="">
var time = 0
<span data-mce-="">this<span data-mce-="">.tableArr.push({ status, info, time: currentTimeFormat })
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>我们需要两个 <span data-mce-=""><strong>Map </strong>数组方便进行取值操作。一个是正常信息数组,一个是异常信息数组。利用随机值当做一个索引,然后取到相对应的状态信息,添加到<span data-mce-=""> <strong>table </strong>中。</span></span></p>
<p>如果当前的<span data-mce-=""> <strong>status </strong>为 <strong><span data-mce-="">normal,</span></strong>说明是正常信息,否则为异常信息。异常信息的话就可以通过<span data-mce-=""> <strong>table.json</strong> 的渲染回调函数 <strong><span data-mce-="">"drawCell": function(g, text, rect, option) { }</span></strong> 来修改它的颜色,使其高亮。</span></span></p>
<p> </p>
<p><strong>偏航系统:风机转动的过程中,随着风的位置的不同,通过偏航系统改变方向。</strong></p>
<p> 效果:</p>
<p> <img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210105120627-828277092.gif"> </p>
<p> <strong>相关伪代码</strong><strong>:</strong></p>
<div class="cnblogs_code">
<pre><span data-mce-="">/*<span data-mce-="">*
* 随机偏航系统
* @param { * }
<span data-mce-="">*/<span data-mce-="">
randomYawSystem() {
var { dm } = <span data-mce-="">this<span data-mce-="">
var { d2d } =<span data-mce-=""> window
var { rangeRandom } =<span data-mce-=""> common
var poll = () =><span data-mce-=""> {
<span data-mce-="">//<span data-mce-=""> 随机数 30 - 50
var random = 30 + rangeRandom(20<span data-mce-="">)
var cabin = dm.getDataByTag('cabin'<span data-mce-="">)<span data-mce-="">
<span data-mce-="">//<span data-mce-=""> 将角度度换算成弧度 然后乘以随机数 实现随机风向
var randomDegrees = translateAngularRadian.radian *<span data-mce-=""> random
var defaultDegress = translateAngularRadian.radian * 180<span data-mce-="">
ht.Default.startAnim({
duration: 1e3<span data-mce-="">,
action: (v) =><span data-mce-=""> {<span data-mce-="">
var oldValue =<span data-mce-=""> cabin.getRotationY()
var newValue =<span data-mce-=""> randomDegrees
var addValue = newValue -<span data-mce-=""> oldValue
cabin.setRotationY(oldValue + addValue *<span data-mce-=""> v)<span data-mce-="">
}
})
}<br><span data-mce-=""> }</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> 上面讲到过角度弧度转换,这里先将随机出的角度转换成弧度,然后赋值,进行旋转。</p>
<p> </p>
<p> <strong>变桨系统:</strong>风速的变化影响风机扇叶的角度。</p>
<p> <strong>效果:</strong></p>
<p><strong><img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210111639668-2124172457.gif"></strong></p>
<p><strong>相关伪代码:</strong></p>
<div class="cnblogs_code">
<pre>var old3Value =<span data-mce-=""> whiteShell3Line.getRotationX()
var old4Value =<span data-mce-=""> whiteShell4Line.getRotationX()
var old5Value =<span data-mce-=""> whiteShell5Line.getRotationX()
<span data-mce-="">//<span data-mce-=""> 指针和扇叶旋转的角度 进行变化
var oldPointerValue = pitchSystem.a('pointer') || 0
<span data-mce-="">//<span data-mce-=""> 风机扇叶和变桨系统的旋转角度
var newRotateAngular = (<span data-mce-="">this.windSpeed - 8) * 7.5 *<span data-mce-=""> translateAngularRadian.radian
var addPointerValue = newRotateAngular -<span data-mce-=""> oldPointerValue
whiteShell3Line.setRotationX(old3Value + ((newRotateAngular -<span data-mce-=""> oldPointerValue)))
whiteShell4Line.setRotationX(old4Value + ((newRotateAngular -<span data-mce-=""> oldPointerValue)))
whiteShell5Line.setRotationX(old5Value + ((newRotateAngular - oldPointerValue)))</span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> 先获取到每个扇叶当前的 X 轴旋转值,再获取到需要旋转的角度值,进行赋值。</p>
<p> </p>
<p> <strong>风机启停:</strong>风机的启动和停止</p>
<p> 相关伪代码:</p>
<div class="cnblogs_code">
<pre>var fanWireframe = d3d.getDataByTag('fanWireframe'<span data-mce-="">)
fanWireframe.setRotationMode('zxy'<span data-mce-="">)
this.allAnimManage = new Map([['fanRotate', null]])<br>
var fanRotating = (easeType) =><span data-mce-=""> {
anim =<span data-mce-=""> ht.Default.startAnim({
duration: 5e3<span data-mce-="">,
easing: (t) =><span data-mce-=""> easeIn(t),
action: (v) =><span data-mce-=""> {<span data-mce-="">
fanWireframe.setRotationZ(fanWireframe.getRotationZ() +<span data-mce-=""> speed)
<span data-mce-="">//<span data-mce-=""> 风机轮毂旋转
<span data-mce-="">if (fanWireframe.getRotationZ() <= -6.28<span data-mce-="">) {
fanWireframe.setRotationZ(0<span data-mce-="">)
}
<span data-mce-="">//<span data-mce-=""> 风机扇叶 uv 偏移
<span data-mce-="">for (let i = 1; i < 17; i++<span data-mce-="">) {
var node =<span data-mce-=""> d3d.getDataByTag(`q${i}`)
node.s('shape3d.uv.offset', fanOffsetData(v))
}
<span data-mce-="">//<span data-mce-=""> 暂停命令
<span data-mce-="">if<span data-mce-=""> (isStop) {
stopFanRotate(fanWireframe.getRotationZ())
anim.pause()
anim = <span data-mce-="">null<span data-mce-="">
}
},
finishFunc: () =><span data-mce-=""> {
fanRotating(<span data-mce-="">false<span data-mce-="">)
}
})
this.allAnimManage.set('fanRotate'<span data-mce-="">, anim)
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> 因为可以启动和停止,那么我们就可以通过控制 <span data-mce-=""><strong>ht.Default.startAnim()</strong> 的返回对象的<span data-mce-=""> <strong>resume</strong> 和 <span data-mce-=""><strong>pause </strong>来达到效果。</span></span></span></p>
<p> 所以我把风机旋转的动画添加到了全局对象中,方便进行调用。</p>
<p> <strong><span data-mce-="">setRotationMode('zxy') </span></strong>方法是设置三维旋转模式,顺序是 <strong><span data-mce-="">z -> x -> y,</span></strong>先进行z轴旋转,再进行x轴旋转,最后进行y轴旋转。设置目的是为了避免坐标轴受外部旋转的影响。</p>
<p> </p>
<p> <strong>风向: 根据风的角度,判断当前是什么位置的风。</strong><strong><br></strong></p>
<p><strong> 效果:</strong></p>
<p><strong><img src="https://img2018.cnblogs.com/i-beta/1496396/201912/1496396-20191210124444726-594901560.png"></strong></p>
<p> <strong>相关伪代码:</strong></p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 判断风向
var windDirection = (rotate) =><span data-mce-=""> {
let direction
<span data-mce-="">switch (<span data-mce-="">true<span data-mce-="">) {
<span data-mce-="">case rotate === 0<span data-mce-="">:
direction = '南'
<span data-mce-="">break
<span data-mce-="">case rotate === 90<span data-mce-="">:
direction = '东'
<span data-mce-="">break
<span data-mce-="">case rotate === 180<span data-mce-="">:
direction = '北'
<span data-mce-="">break
<span data-mce-="">case rotate === 240<span data-mce-="">:
direction = '西'
<span data-mce-="">break
<span data-mce-="">case rotate > 0 && rotate < 90<span data-mce-="">:
direction = '东南'
<span data-mce-="">break
<span data-mce-="">case rotate > 90 && rotate < 180<span data-mce-="">:
direction = '东北'
<span data-mce-="">break
<span data-mce-="">case rotate > 180 && rotate < 270<span data-mce-="">:
direction = '西北'
<span data-mce-="">break
<span data-mce-="">case rotate > 270 && rotate < 360<span data-mce-="">:
direction = '西南'
<span data-mce-="">break
<span data-mce-="">default<span data-mce-="">:
direction = '没有找到风向'<span data-mce-="">
}
<span data-mce-="">return<span data-mce-=""> direction
}
<span data-mce-="">//<span data-mce-=""> 判断是哪个方向
var angular = randomDegrees *<span data-mce-=""> translateAngularRadian.angular
var direction =<span data-mce-=""> windDirection(angular)</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> 将罗盘的指针角度放到 <span data-mce-=""><strong>switch</strong> 进行判断,如果找到对应的风向就返回。</span></p>
<p> </p>
<p><span data-mce-=""><strong>总结</strong></span></p>
<p>风力发电是一个工业互联网的典型例子,我们可以通过对风机模型或者监测数据进行分析,可以减轻我们工作复杂程度,帮助我们快速了解发电内部结构及发电功能。</p>
<p><strong>HT </strong>能做的东西远远不止于此,这需要我们丰富的想象力以及自身过硬的技术。我希望可以通过这篇文章向大家传递一种能量,让大家更有兴趣、迸发更多新鲜的想法,去做更多好玩的东西。</p>
<p> </p>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/xhload3d/p/12131708.html
頁:
[1]