Technology frontier: 3D wind farm based on HTML5 WebGL

Preface

Wind energy is a kind of clean energy in development, it is inexhaustible and inexhaustible. Of course, when building a wind farm, the meteorological conditions and social natural conditions should be considered first. In recent years, my country's offshore and onshore wind power has developed rapidly. Sea water and land provide a good geological guarantee for our wind power generation. It is these sites that provide inexhaustible energy for our wind power. Now we are working hard to explore these areas.

For the article I shared today, I used  a visualization system built by  the  HT for Web  product of Hightopo , which realized the overall flow of the wind farm and allowed everyone to see a complete wind power preview system.

Rough process

Below is the flow chart of the entire project. We can enter the site distribution page and the centralized control page from the home page.

The site distribution page also includes two different 3D scenes, namely the land wind airport and the offshore wind airport. Clicking on the two 3D wind fields will eventually enter the 3D wind turbine scene.

Technology frontier: 3D wind farm based on HTML5 WebGL

 

Implementation analysis:

Home page:

1. World map effect

Technology frontier: 3D wind farm based on HTML5 WebGL

Dynamic operation preview address: https://www.hightopo.com/demos/index.html

2. China map effect

Technology frontier: 3D wind farm based on HTML5 WebGL

 

3. City map effect

Technology frontier: 3D wind farm based on HTML5 WebGL

 

Centralized control center page (no animation effect):

Technology frontier: 3D wind farm based on HTML5 WebGL

 

Field distribution page (no animation effect):

Technology frontier: 3D wind farm based on HTML5 WebGL

 

Landwind Airport:

Technology frontier: 3D wind farm based on HTML5 WebGL

 

Haifeng Airport:

Technology frontier: 3D wind farm based on HTML5 WebGL

 

Code

We can see that the earth on the homepage has three perspective states, world map, China map, and city map. Click each state camera will go to the corresponding position. Before that, we need to save the corresponding center  and  eye in advance  .

We'd better create a new  data.js  file, dedicated to provide data.

The relevant pseudo code is as follows:

// 记录位置
var cameraLocations = {
    earth: {
        eye: [-73, 448, 2225],
        center: [0, 0, 0]
    },

    china: {
        eye: [-91, 476, 916],
        center: [0, 0, 0]
    },

    tsankiang: {
        eye: [35, 241, 593],
        center: [0, 0, 0]
    }
};

Okay, with the data. We should listen to events next. We can click the button, or click the highlighted area (the world map can only be clicked) to enter the China map perspective.

Technology frontier: 3D wind farm based on HTML5 WebGL

 

We can get these two nodes first, and then perform the same processing on their click events. However, I think this approach can be optimized and replaced with a new way of thinking.

We can filter the events first. We create two arrays. One holds the  executable events like  click and onEnter , and the other holds all the nodes that can trigger the event. This can help us maintain, but also make the structure clearer.

In the figure below, we can see that if the current node does not have event permission or the current event itself does not have permission, it will be filtered out. If all can be returned correctly, execute the corresponding event.

Technology frontier: 3D wind farm based on HTML5 WebGL

 

As long as the node we are currently executing meets the requirements, we will pass the event (currently executing event) and tag (node ​​tag) to the execution function nodeEvent for execution. In this way, resources will not be wasted to deal with invalid events or nodes.

Let's take a look at how  nodeEvent  handles it next!

The relevant pseudo code is as follows:

/**
 * 气泡事件
 * @param { string } event 当前事件
 * @param { string } propertyName 当前节点标签
 */
bubblesEvent(event, propertyName) {
    var dm = this.dm
    var account = dm.getDataByTag('account')
    var currentNode = dm.getDataByTag(propertyName)
    var self = this

    var clickData = function() {
        // 执行清除动作
        self.clearAction()
    }

    var onEnter = function() {
       // do something
    }

    var onLeave = function() {
    // do something
    }

    var allEvent = { clickData, onEnter, onLeave }

    allEvent[event] && allEvent[event]()
}

As you can see, we can use  propertyName (node ​​label)  string concatenation to form a method name. For example, the node label currently obtained is  bubbles, after  this[`${properName }Event`] the method this['bubblesEvent']  is obtained. Of course, this method is defined in advance.

In the specific node method, we created the corresponding event function. According to the passed  event  to determine whether there is a corresponding method. Execute if there is any, otherwise return  false  . The advantages of this are: decoupling, simple structure, and quick positioning of problems.

However, if we think about it, when we click on the world map and China map, the functions are similar! If we can merge them, it will be much more convenient! ! Let's modify the code.

The relevant pseudo code is as follows:

/**
  * 执行节点事件
  */
function nodeEvent(event, propertyName) {
    // 过滤是否有可以合并的事件
    var filterEvents = function(propertyName) {
        var isCombine = false
     var self = this
        if (['earth', 'china'].includes(propertyName)) {
            self.changeCameraLocaltions(event, propertyName)
            isCombine = true
        }

        return !isCombine
    }
   var eventFun = this[`${propertyName}Event`]
   // 执行对应的节点事件
   filterEvents(propertyName) &&
   eventFun &&
   eventFun(event, propertyName)
}

We judge in advance whether the current event can be merged, and return false if it can, no longer execute the following code, and then execute our own function.

At this time, we can get the corresponding center and eye from the cameraLocations variable of data.js through the corresponding node label.

The moving camera animation uses  the  moveCamera  method of  gv , which accepts 3 parameters, eye (camera) , center (target), and animConfig (animation configuration). Then we return the current animation to   the  moveCameraAnim  property of globalAnim to facilitate us to clean up.

The next step is to switch pages, which requires great care. Because once a certain attribute is not cleared, it will cause memory leaks and other problems, and the performance will become slower and slower. Will cause the page to freeze!

So we need a function clearAction dedicated to clearing the data model  . We should put all animation objects into an object or array. This is convenient for cleaning up when switching pages.

The relevant pseudo code is as follows:

/**
 * 清除动作
 */
clearAction(index) {
    var { dm, gv } = this
    var { g3d, d3d } = window

    allListener.mi3d && g3d.umi(allListener.mi3d)
    allListener.mi2d && gv.umi(allListener.mi2d)
    dm.removeScheduleTask(this.schedule)

    dm && dm.clear()
    d3d && d3d.clear()

    window.d3d = null
    window.dm = null

    for (var i in globalAnim) {
        globalAnim[i] && globalAnim[i].pause()
        globalAnim[i] = null
    }

    // 清除对应的 3D 图纸
    ht.Default.removeHTML(g3d)

    gv.addToDOM()
    ht.Default.xhrLoad(`displays/HT-project_2019/风电/${index}.json`, function (text) {
        let json = ht.Default.parse(text)
        gv.deserialize(json, function(json, dm2, gv2, datas) {
            if (json.title) document.title = json.title

            if (json.a['json.background']) {
                let bgJSON = json.a['json.background']
                if (bgJSON.indexOf('scenes') === 0) {
                    var bgG3d

                    if (g3d) {
                        bgG3d = g3d
                    } else {
                        bgG3d = new ht.graph3d.Graph3dView()
                    }

                    var bgG3dStyle = bgG3d.getView()
                    bgG3dStyle.className = index === 1 ? '' : index === 3 ? 'land' : 'offshore'

                    bgG3d.deserialize(bgJSON, function(json, dm3, gv3, datas) {
                        init3d(dm3, gv3)
                    })

                    bgG3d.addToDOM()
                    gv.addToDOM(bgG3dStyle)
                }
                gv.handleScroll = function () {}
            }

            init2d(dm2, gv2)
        })
    })
}

First, we need to  clear dm (data model)  and  gv (drawing)  . Also note: mi (monitor function) , Schedule (scheduled tasks)  should  dm.clear ()  before  the Remove . All animations perform  stop()  operation, and then set its value to  null  . It should be noted here that  after the stop is executed  , the finishFunc  callback function will be called once  .

When our 2D drawing contains a 3D background, we need to determine whether a 3D instance already exists, and if there is, we don’t need to create it again. Interested in learning about  webGL  application memory leaks.

When entering two 3D scenes, we need an opening animation, like the gif at the beginning. So we need to  save the center  and  eye of the two opening animations in the  cameraLocations  we have defined  .

offshoreStart, offshoreEnd, landStart, and landEnd indicate the start and end positions of offshore and onshore power plants.

We need to determine whether the current loading is an offshore power plant or an onshore power plant. We can add className when loading the corresponding drawing  .

We have defined the index parameter in the  clearAction  function. If you click on a land power plant, the number 3 is passed, and if it is an offshore power plant, it is the number 4.

For example, if I need to load a land power plant, then I can add  className by judging  g3d.className = index === 3?'Land':'offshore'  .

Then perform initialization judgment in init.

The relevant pseudo code is as follows:

function init() {
    var className = g3d.getView().className

    // 执行单独的事件
    this.selfAnimStart(className)

    this.initData()

    // 监听事件
    this.monitor()
}

We get the corresponding  className, pass in the corresponding type and execute the corresponding initialization event,  and animate the camera through the moveCameraAnim function we have defined  .

The relevant pseudo code is as follows:

/**
  * 不同风电场的开场动画
  */
function selfAnimStart(type) {
    var gv = this.gv
    var { eye, center } = cameraLocations[`${type}End`]
    var config = {
            duration: 3000,
            eye,
            center,
         }

    this.moveCameraAnim(gv, config)
}

to sum up

After finishing this project, I got a lot of growth and insights. A good way for the rapid growth of technology is to constantly dig out the details. The project is a work of art and needs to be polished continuously until you are satisfied. Every subtle point will affect the performance behind. Therefore, we should do everything in the spirit of a craftsman.

Of course, I also hope that some partners can dare to explore the industrial Internet field. We can achieve far more than this. This requires our imagination to add more fun and practical demos to this field. And also learn a lot of knowledge in the industrial field.

Guess you like

Origin blog.csdn.net/iotopo/article/details/108193597