基於 HTML5 WebGL 的 3D 科幻風機

圖撲軟體發表於2020-04-06

前言

  許多世紀以來,風力機同水力機械一樣,作為動力源替代人力、畜力,對生產力的發展發揮過重要作用。近代機電動力的廣泛應用以及二十世紀50年代中東油田的發現,使風機發電機的發展緩慢下來。

70年代初期,由於“石油危機”,出現了能源緊張的問題,人們認識到常規礦物能源供應的不穩定性和有限性,於是尋求清潔的可再生能源遂成為現代世界的一個重要課題。風能作為可再生的、無汙染的自然能源又重新引起了人們重視。

現在我希望可以通過這個風機 demo 使風力發電機的各個功能近距離的展示給大家,使大家能更瞭解風力發電機。

本 demo 使用 HT for Web 產品輕量化 HTML5/WebGL 建模的方案。

demo 連結:https://hightopo.com/demo/fan3d-magic/

 

 

風機主要功能介紹

效果:

  

 

周圍環境功能:

  一.  風速值。

  虛擬一個風速值,風速值會影響風機的發電效率和變槳系統的變化。

 

  二.  資料統計

  

  環境溫度、機艙溫度、齒輪箱溫度、風速的圖形百分比會隨著時間發生改變。

  三.  環境監測

  

  左邊對風機的各個引數實施了監測,右面是風速變化的折線圖。

 

  四.  資料監測  

  

  風機在發電的過程中發生的異常情況,發生的故障部位及故障發生的時間。異常資訊的收集有利於人們進行異常分析以及異常處理。

  五. 偏航系統

  

  偏航系統,又稱對風裝置,是風力發電機機艙的一部分,其作用在於當風速向量的方向變化時,能夠快速平穩地對準風向,以便風輪獲得最大的風能。

 

  . 變槳系統

  

  變槳系統作為大型風電機組控制系統的核心部分之一,對機組安全、穩定、高效的執行具有十分重要的作用。穩定的變槳控制已成為當前大型風力發電機組控制技術研究的熱點和難點之一。

  變槳控制技術簡單來說,就是通過調節槳葉的節距角,改變氣流對槳葉的攻角,進而控制風輪捕獲的氣動轉矩和氣動功率。

 

按鈕控制功能:

 

  風機啟停:

  

  線框模式:

  

  機艙視角:

  

  發電工藝:

  

 

整理思路:

    場景部分

  這裡把 3D 當做背景巢狀在 2D 場景中。

   這樣在初始化圖紙的時候,直接反序列化 2D 圖紙即可。

 

  事件部分

  2D 圖紙中有很多的按鈕,通過它們來控制 3D 中的一些動畫。

  實現思路是在反序列化圖紙的時候把 2D、3D 的 模型和檢視物件掛載到 window 上,這樣在不同的場景中都可以獲取到相應的資料模型。

 

  環境部分:

  風速、風向、變槳角度這些會在 2D、3D 中所表現,所以可以把他們放到資料池裡面,方便管理。

 

具體程式碼實現:

場景搭建:上面說了,我們把 3D 當做背景巢狀在 2D 中,所以只需要序列化 2D 即可,裡面需要進行背景判斷的部分程式碼。

相關虛擬碼

graphView.deserialize('displays/demo/風力發電機/風力發電機結構檢視.json', function (json, dm, gv, datas) {
  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 () { }
        }
 })

 

模擬風速:每隔30s,隨機產生一個值,當做風速值。

相關虛擬碼

// 模擬風速
mockWindSpeed() {
  return 8 + Math.random() * 12
}

 

資料統計:每隔30s,隨機變換。

效果:

相關虛擬碼

// 指標和扇葉旋轉的角度 進行變化
var oldPointerValue = pitchSystem.a('pointer') || 0

// 風機扇葉和變槳系統的旋轉角度
var newRotateAngular = (this.windSpeed - 8) * 7.5 * translateAngularRadian.radian
var addPointerValue = newRotateAngular - oldPointerValue

var oldWindSpeedClip = environmentalData.a('windSpeedClip') || 0
var newWindSpeedClip = (this.windSpeed - 8) / 12
var addWindSpeedClip = newWindSpeedClip - oldWindSpeedClip

var anim = {
  duration: 1e3,
  easing: (v) => {
    return v * v
   },
  action: (v) => {
    var windSpeed = Number(this.windSpeed.toFixed(2))
    var Max = Number(MaxValue.toFixed(2))
    var average = Number(Aver.toFixed(2))
    var windSpeedClip = oldWindSpeedClip + (addWindSpeedClip * v)
    // 設定發電引數隨機資料
     generator.a({ windSpeed })
    // 設定環境監測隨機資料
    environmentalData.a({ windSpeed, windSpeedClip })
    // 設定統計引數隨機資料
    statisticalParam.a({ average, Max, windSpeed })
    // 設定變航系統的指標角度
    pitchSystem.a('pointer', oldPointerValue + (addPointerValue * v))
  }
}

ht.Default.startAnim(anim)

這裡涉及到角度和弧度的轉換。1° = Math.PI / 180°,1rad = 180° / Math.PI,因為場景中使用的是弧度制,所以需要把隨機出的角度值轉換成弧度。

這裡解釋下程式碼,先獲取到當前的值。然後在加上 隨機值 - 當前值。比如當前值為 16,隨機出的數值有兩種情況,1:比當前值大。2:比當前值小。

如果比當前值大的話,比如18,那麼就是這樣 16 + (18 - 16) * v ( easing 函式運算後的值)

如果比當前值小的話,比如13,那麼就是這樣 16 + (13 - 16) * v ( easing 函式運算後的值)

這樣當隨機的時候,就會從當前值平滑的改變到目標值。

 

資料統計:每隔30s,監測當前風機的故障資訊。

效果: 

 這裡使用了 table.json 檔案,通過修改 ht.dataSource 屬性新增實時資訊。

 相關虛擬碼

var checkInternals = () => {
  /**
   * 故障資訊
   * 變槳系統 主軸 偏航系統 齒輪箱 油冷裝置 發電機 風冷裝置
   */
    var FailureStatus = {
        // 正常狀態
        status1: new Map([
            [0, ['艙內溫度正常,變槳角度正常。', 'i10']],
            [1, ['艙內溫度正常,主軸轉速正常。', 'i9']],
            [2, ['偏航系統精確。', 'i8']],
            [3, ['齒輪箱溫度正常。', 'i1']],
            [4, ['油冷裝置溫控表正常。', 'i3']],
            [5, ['發電機功率正常。', 'i5']],
            [6, ['風冷裝置正常。', 'i2']]
        ]),

        // 異常狀態
        status2: new Map([
            [0, ['變槳角度異常。', 'i10']],
            [1, ['主軸轉速偏高。', 'i9']],
            [2, ['偏航系統出現偏移。', 'i8']],
            [3, ['齒輪箱溫度偏高。', 'i1']],
            [4, ['油冷裝置內積塵過多。', 'i3']],
            [5, ['發電機電流過大。', 'i5']],
            [6, ['風冷裝置散熱不足。', 'i2']]
        ]),
    }

    // 返回裝置正常的狀況 status 1: 正常 2: 不正常
    var mockQquipmentFailure = (status) => {
        var { rangeRandom } = common
        var index = rangeRandom(7)

        // 返回隨機出來的裝置情況
        return FailureStatus[`status${status}`].get(index)
    }

      var info = randomInfo[0]
      var targetTag = randomInfo[1]

      this.tableArr = table.a('ht.dataSource')
      var currentTimeFormat = DateUtil.formatHourTime(new Date())

  // 預設是正常 如果找到故障關鍵字的話 賦值為 異常
  var status = 'normal'
  var time = 0
 this.tableArr.push({ status, info, time: currentTimeFormat })
}

我們需要兩個 Map 陣列方便進行取值操作。一個是正常資訊陣列,一個是異常資訊陣列。利用隨機值當做一個索引,然後取到相對應的狀態資訊,新增到 table 中。

如果當前的 status 為 normal,說明是正常資訊,否則為異常資訊。異常資訊的話就可以通過 table.json 的渲染回撥函式 "drawCell": function(g, text, rect, option) { } 來修改它的顏色,使其高亮。

 

偏航系統:風機轉動的過程中,隨著風的位置的不同,通過偏航系統改變方向。

 效果:

   

 相關虛擬碼

/**
  * 隨機偏航系統
  * @param { * }
  */
  randomYawSystem() {
    var { dm } = this
    var { d2d } = window
    var { rangeRandom } = common

    var poll = () => {
       // 隨機數 30 - 50
        var random = 30 + rangeRandom(20)
        var cabin = dm.getDataByTag('cabin')

        // 將角度度換算成弧度 然後乘以隨機數 實現隨機風向
        var randomDegrees = translateAngularRadian.radian * random
        var defaultDegress = translateAngularRadian.radian * 180

         ht.Default.startAnim({
           duration: 1e3,
           action: (v) => {
             var oldValue = cabin.getRotationY()
             var newValue = randomDegrees
             var addValue = newValue - oldValue

           cabin.setRotationY(oldValue + addValue * v)
         }
      })
    }
 }

 上面講到過角度弧度轉換,這裡先將隨機出的角度轉換成弧度,然後賦值,進行旋轉。

 

 變槳系統:風速的變化影響風機扇葉的角度。

 效果:

相關虛擬碼:

var old3Value = whiteShell3Line.getRotationX()
var old4Value = whiteShell4Line.getRotationX()
var old5Value = whiteShell5Line.getRotationX()

// 指標和扇葉旋轉的角度 進行變化
var oldPointerValue = pitchSystem.a('pointer') || 0
// 風機扇葉和變槳系統的旋轉角度
var newRotateAngular = (this.windSpeed - 8) * 7.5 * translateAngularRadian.radian
var addPointerValue = newRotateAngular - oldPointerValue

whiteShell3Line.setRotationX(old3Value + ((newRotateAngular - oldPointerValue)))
whiteShell4Line.setRotationX(old4Value + ((newRotateAngular - oldPointerValue)))
whiteShell5Line.setRotationX(old5Value + ((newRotateAngular - oldPointerValue)))

 先獲取到每個扇葉當前的 X 軸旋轉值,再獲取到需要旋轉的角度值,進行賦值。

 

 風機啟停:風機的啟動和停止

 相關虛擬碼:

var fanWireframe = d3d.getDataByTag('fanWireframe')
fanWireframe.setRotationMode('zxy')
this.allAnimManage = new Map([['fanRotate', null]])

var fanRotating = (easeType) => {
  anim = ht.Default.startAnim({
    duration: 5e3,
    easing: (t) => easeIn(t),
    action: (v) => {
      fanWireframe.setRotationZ(fanWireframe.getRotationZ() + speed)

        // 風機輪轂旋轉
        if (fanWireframe.getRotationZ() <= -6.28) {
          fanWireframe.setRotationZ(0)
        }

        // 風機扇葉 uv 偏移
        for (let i = 1; i < 17; i++) {
          var node = d3d.getDataByTag(`q${i}`)
          node.s('shape3d.uv.offset', fanOffsetData(v)[i - 1])
        }

        // 暫停命令
        if (isStop) {
          stopFanRotate(fanWireframe.getRotationZ())
          anim.pause()
          anim = null
        }
    },
      finishFunc: () => {
        fanRotating(false)
      }
    })

    this.allAnimManage.set('fanRotate', anim)
}

 因為可以啟動和停止,那麼我們就可以通過控制 ht.Default.startAnim() 的返回物件的 resume 和 pause 來達到效果。

 所以我把風機旋轉的動畫新增到了全域性物件中,方便進行呼叫。

 setRotationMode('zxy') 方法是設定三維旋轉模式,順序是 z -> x -> y,先進行z軸旋轉,再進行x軸旋轉,最後進行y軸旋轉。設定目的是為了避免座標軸受外部旋轉的影響。

 

 風向: 根據風的角度,判斷當前是什麼位置的風。

 效果:

 相關虛擬碼:

// 判斷風向
var windDirection = (rotate) => {
    let direction

    switch (true) {
    case rotate === 0:
        direction = '南'
        break
    case rotate === 90:
        direction = '東'
        break
    case rotate === 180:
        direction = '北'
        break
    case rotate === 240:
        direction = '西'
        break
    case rotate > 0 && rotate < 90:
        direction = '東南'
        break
    case rotate > 90 && rotate < 180:
        direction = '東北'
        break
    case rotate > 180 && rotate < 270:
        direction = '西北'
        break
    case rotate > 270 && rotate < 360:
        direction = '西南'
        break
    default:
        direction = '沒有找到風向'
    }

    return direction
}
// 判斷是哪個方向
var angular = randomDegrees * translateAngularRadian.angular
var direction = windDirection(angular)

 將羅盤的指標角度放到 switch 進行判斷,如果找到對應的風向就返回。

 

總結

風力發電是一個工業網際網路的典型例子,我們可以通過對風機模型或者監測資料進行分析,可以減輕我們工作複雜程度,幫助我們快速瞭解發電內部結構及發電功能。

HT 能做的東西遠遠不止於此,這需要我們豐富的想象力以及自身過硬的技術。我希望可以通過這篇文章向大家傳遞一種能量,讓大家更有興趣、迸發更多新鮮的想法,去做更多好玩的東西。

相關文章