Foreword
Wind energy is a kind of clean energy under development, which is inexhaustible and inexhaustible. Of course, the construction of wind farms should first consider meteorological conditions and social natural conditions. In recent years, China's offshore and onshore wind power has developed rapidly. Seawater 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.
This article implements the overall process of the wind farm. It allows everyone to see a complete wind power preview system.
It should be noted that this project was built using Hightopo 's HT for Web product.
Preview address: hightopo.com/demo/wind-p ...
General process
The following is a flowchart of the entire project. We can enter the site distribution page and centralized control page from the home page.
The site distribution page includes two different 3D scenes, namely the land-wind airport and the sea-wind airport. Clicking on two 3D wind airports will eventually enter the 3D wind turbine scene.
Preview effect
Home:
1. World map effect
2. China map effect
2. City map effect
Central control center page (without animation effect):
Site distribution page (without animation effect) :
Landwind Airport:
Seawind Airport:
Code
We can see that the earth on the homepage has three perspective states: world map, China map, and city map. Click each status camera to go to the corresponding position. Before that, we have to pre-store the corresponding center and eye.
We'd better create a new data.js file, specifically used to provide data.
The relevant pseudocode 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]
}
}
Ok, after having the data. We should listen to the event next. We can click on the button or click on the highlighted area (only the button can be clicked on the world map) to enter the perspective of China map.
We can get these two nodes first, and then do the same to their click events. However, I think this method can be optimized and replaced with a way of thinking.
We can filter the events first. We create two arrays, one holds events that can be executed like click and onEnter , and one holds all nodes that can trigger events. This can help us maintain and make the structure clearer.
In the figure below, we can see that if the current node does not have event permissions or the current event itself has no permissions, it will be filtered out. If both can return correctly, the corresponding event is executed.
The relevant pseudocode is as follows:
// 权限事件
this.eventMap = {
clickData: true,
onEnter: true,
onLeave: true
}
// 权限节点
this.nodeMap = {
outline: true,
outline2: true,
earth: true,
bubbles: true,
circle: true
}
/**
* 监听事件
*/
initMonitor() {
var gv = this.gv
var self = this
var evntFlow = function (e) {
var event = e.kind
var tag = e.data && e.data.getTag()
// 检查当前事件或者节点是否能够被执行
if (!self.eventMap[event] && !self.nodeMap[tag]) return false
self.nodeEvent(event, tag)
}
gv.mi(eventFlow)
}
As long as the node we want to execute currently meets the requirements, we will pass the event (currently executed event) and tag (node tag) to the execution function nodeEvent for execution **. ** This will not waste resources to deal with those invalid events or nodes.
Let's take a look at how to handle nodeEvent !
The relevant pseudocode 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 the propertyName (node label) string to form a method name. For example, the current node label is bubbles. After this [`$ {properName} Event`] , the method is this ['bubblesEvent'] . 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, otherwise return false . The advantages of this are: decoupling, simple structure, and quick positioning of problems.
However, if we think about it carefully, when we click on the world map and the China map, the functions are almost the same! If we can merge them, it will be much more convenient! ! Let's modify the code.
The relevant pseudocode is as follows:
/**
* 执行节点事件
*/
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 determine in advance whether the current event can be merged, if it returns ** false **, no longer execute the following code, and then execute our own function.
At this time, we can pass the corresponding node label, from data.js the cameraLocations taken to the corresponding variable center, eye.
The relevant pseudocode is as follows:
/**
* 移动镜头动画
* @param { object } config 坐标对象
*/
moveCameraAnim(gv, config) {
var eye = config.eye var center = config.center // 如果动画已经存在,进行清空
if(globalAnim.moveCameraAnim) {
globalAnim.moveCameraAnim.stop() globalAnim.moveCameraAnim = null
}
var animConfig = {
duration: 2e3
}
globalAnim.moveCameraAnim = gv.moveCamera(eye, center, animConfig)
}
// 需要改变相机位置
changeCameraLocaltions(event, properName) {
var config = cameraLocations[properName]
// 移动相机
this.moveCameraAnim(this.gv, config)
}
The moving camera animation uses gv 's moveCamera method, 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 our cleanup.
Next, it's time to switch pages, which requires great care. Because once a certain attribute is not cleared, it will cause problems such as memory leaks, and performance will become slower and slower. It will cause the page to freeze!
So we need a function ** clearAction specifically for clearing the data model. ** We should put all the animation objects in one object or array. This is convenient for cleaning up when switching pages.
The relevant pseudocode 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 remove dm (data model) and gv (drawing) . Also note: mi (monitor function) , Schedule (scheduled tasks) should dm.clear () before the Remove . All animations perform a stop () operation, and then set its value to null . It should be noted here that after executing stop , 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 it does exist, we do not need to create it again. If you are interested, you can learn about the webGL application memory leak problem.
When entering two 3D scenes, we need an opening animation, like the gif at the beginning. Therefore, we need to save the center and eye of the two opening animations to the cameraLocations we have defined .
// 记录位置
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]
},
offshoreStart: {
eye: [-849, 15390, -482],
center: [0, 0, 0]
},
landStart: {
eye: [61, 27169, 55],
center: [0, 0, 0]
},
offshoreEnd: {
eye: [-3912, 241, 834],
center: [0, 0, 0]
},
landEnd: {
eye: [4096, 4122, -5798],
center: [1261, 2680, -2181]
}
}
offshoreStart, offshoreEnd, landStart, and landEnd represent the start and end positions of offshore and onshore power plants **. **
We need to determine whether the current load is an offshore power plant or an onshore power plant. We can add className when loading the corresponding drawings .
We have defined the index parameter in the clearAction function. If you click on the land power plant, it will be the number 3, if it is the offshore power plant, it will be the number 4.
For example, if I need to load a land farm, then I can add className by judging g3d.className = index === 3? 'Land': 'offshore' .
Then make initialization judgment in init.
The relevant pseudocode is as follows:
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 the camera animation is performed by the moveCameraAnim function we have defined .
The relevant pseudocode is as follows:
/**
* 不同风电场的开场动画
*/
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
This project gave us a better understanding of wind power. Whether it is the regional advantages of the wind farm, or the structure and operation principle of the wind turbine.
After finishing this project, I have gained a lot of growth and insight. A good way for the rapid growth of technology is to constantly pick the details. The project is a work of art, which needs to be polished continuously until it is satisfactory. Each subtle point will affect the performance behind. Therefore, we should do anything with the spirit of a craftsman.
Of course, I also hope that some partners will have the courage to explore the field of industrial Internet. We can achieve much more than that. This requires our imagination to add more fun and practical demos to this field. And can also learn a lot of knowledge in industry.