Sistema de visualización de parques eólicos 3D basado en HTML5 + WebGL

Prólogo

La energía eólica es un tipo de energía limpia en desarrollo, que es inagotable e inagotable. Por supuesto, la construcción de parques eólicos primero debe considerar las condiciones meteorológicas y las condiciones sociales naturales. En los últimos años, la energía eólica marina y terrestre de China se ha desarrollado rápidamente. El agua de mar y la tierra proporcionan una buena garantía geológica para nuestra generación de energía eólica. Son estos sitios los que proporcionan energía inagotable para nuestra energía eólica. Ahora estamos trabajando duro para explorar estas áreas.

Este artículo implementa el proceso general del parque eólico. Permite a todos ver un sistema completo de vista previa de energía eólica.

Cabe señalar que este proyecto es el uso de  Hightopo  de  TH para Web  productos para construir.

Dirección de vista previa: hightopo.com/demo/wind-p ...

Proceso general

El siguiente es un diagrama de flujo de todo el proyecto. Podemos ingresar a la página de distribución del sitio y a la página de control centralizado desde la página de inicio.

La página de distribución del sitio incluye dos escenas 3D diferentes, a saber, el aeropuerto de viento de tierra y el aeropuerto de viento de mar. Al hacer clic en dos aeropuertos eólicos en 3D, finalmente entrará en la escena de las turbinas eólicas en 3D.

Efecto de vista previa

Inicio

1. Efecto del mapa mundial

2. Efecto del mapa de China

2. Efecto del mapa de la ciudad

Página del centro de control central (sin efecto de animación):

Página de distribución del sitio (sin efecto de animación) :

Aeropuerto Landwind:

Aeropuerto Seawind:

Implementación de código

Podemos ver que la tierra en la página de inicio tiene tres estados de perspectiva: mapa mundial, mapa de China y mapa de la ciudad. Haga clic en cada cámara de estado para ir a la posición correspondiente. Antes de eso, tenemos que almacenar previamente el centro  y el  ojo correspondientes  .

Será mejor que creemos un nuevo   archivo data.js , específicamente utilizado para proporcionar datos.

El pseudocódigo relevante es el siguiente:

// 记录位置
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, después de tener los datos. Deberíamos escuchar el evento a continuación. Podemos hacer clic en el botón o en el área resaltada (solo se puede hacer clic en el botón en el mapa mundial) para ingresar a la perspectiva del mapa de China.

Podemos obtener estos dos nodos primero, y luego hacer lo mismo con sus eventos de clic. Sin embargo, creo que este método puede optimizarse y reemplazarse por una forma de pensar.

Primero podemos filtrar los eventos. Creamos dos matrices, una contiene  eventos que se pueden ejecutar como  click y onEnter , y otra contiene todos los nodos que pueden desencadenar eventos. Esto puede ayudarnos a mantener y aclarar la estructura.

En la figura a continuación, podemos ver que si el nodo actual no tiene permisos de evento o el evento actual en sí no tiene permisos, se filtrará. Si ambos pueden regresar correctamente, se ejecuta el evento correspondiente.

El pseudocódigo relevante es el siguiente:

// 权限事件
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)
}    

Mientras el nodo que estamos ejecutando actualmente cumpla con los requisitos, pasaremos el  evento  (evento ejecutado actualmente)  y la  etiqueta (etiqueta de nodo)  a la función de ejecución  nodeEvent para  ejecutar **. ** Esto no desperdiciará recursos para lidiar con esos eventos o nodos no válidos.

¡Echemos un vistazo a   cómo manejar nodeEvent !

El pseudocódigo relevante es el siguiente:

/**
 * 气泡事件
 * @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]()
}

Como puede ver, podemos usar la   cadena propertyName (etiqueta de nodo) para formar un nombre de método. Por ejemplo, la etiqueta del nodo actual es  burbujas. Después de  este [`$ $ properName} Evento`]  ,  el método es  este ['bubblesEvent'] . Por supuesto, este método se define de antemano.

En el método de nodo específico, creamos la función de evento correspondiente. De acuerdo con el evento pasado   para determinar si hay un método correspondiente. Ejecutar si hay, de lo contrario devolver  falso  . Las ventajas de esto son: desacoplamiento, estructura simple y posicionamiento rápido de problemas.

Sin embargo, si lo pensamos cuidadosamente, cuando hacemos clic en el mapa mundial y el mapa de China, ¡las funciones son casi las mismas! Si podemos fusionarlos, ¡será mucho más conveniente! ! Modifiquemos el código.

El pseudocódigo relevante es el siguiente:

/**
  * 执行节点事件
  */
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)
}

Determinamos de antemano si el evento actual se puede combinar, si devuelve ** falso **, ya no ejecutamos el siguiente código y luego ejecutamos nuestra propia función.

En este momento, podemos pasar la etiqueta nodo correspondiente, desde  data.js  los  cameraLocations  adoptadas para la variable correspondiente  centro, ojo.

El pseudocódigo relevante es el siguiente:

/**
 * 移动镜头动画
 * @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)
}

La animación de cámara en movimiento utiliza   el   método moveCamera de gv , que acepta 3 parámetros, ojo (cámara) , ** centro (objetivo) y animConfig (configuración de animación). ** Luego ponemos la parte posterior animación actual a la  globalAnim  de  moveCameraAnim  atribuye a ayudarnos a limpiar.

Luego, es hora de cambiar de página, lo que requiere mucho cuidado. Porque una vez que un determinado atributo no se borra, causará problemas tales como pérdidas de memoria y el rendimiento será cada vez más lento. ¡Hará que la página se congele!

Por lo tanto, necesitamos una función ** clearAction específicamente para borrar el modelo de datos. ** Deberíamos poner todos los objetos de animación en un objeto o matriz. Esto es conveniente para limpiar al cambiar de página.

El pseudocódigo relevante es el siguiente:

/**
 * 清除动作
 */
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)
        })
    })
}

Primero, necesitamos eliminar  dm (modelo de datos)  y  gv (dibujo)  . También tenga en cuenta: mi (visualización) , Horario (tareas programadas)  debe  dm.clear ()  antes de  la opción Quitar . Todas las animaciones realizan una   operación stop () y luego establecen su valor en  nulo  . Cabe señalar aquí que  después de ejecutar  stop , la  función de devolución de llamada finishFunc se llamará una vez  .

Cuando nuestro dibujo 2D contiene un fondo 3D, necesitamos determinar si ya existe una instancia 3D, y si existe, no necesitamos crearla nuevamente. Si está interesado, puede obtener información sobre el problema de pérdida de memoria de la aplicación webGL.

Al ingresar dos escenas 3D, necesitamos una animación de apertura, como el gif al principio. Por lo tanto, debemos guardar el centro y el ojo de las dos animaciones de apertura en las ubicaciones de la cámara que hemos definido   .

// 记录位置
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 y landEnd  representan las posiciones inicial y final de las plantas de energía en alta mar y en tierra **. ** **

Necesitamos determinar si la carga actual es una planta de energía en alta mar o una planta de energía en tierra. Podemos agregar className al cargar los dibujos correspondientes  .

Hemos  definido el parámetro de índice en la  función clearAction . Si hace clic en la planta de energía terrestre, se pasa el número 3. Si es una planta de energía en alta mar, es el número 4.

Por ejemplo, si necesito cargar una granja de tierra, entonces puedo  agregar  className al juzgar  g3d.className = index === 3? 'Land': 'offshore' .

Luego haga un juicio de inicialización en init.

El pseudocódigo relevante es el siguiente:

init() {
    var className = g3d.getView().className
    
    // 执行单独的事件
    this.selfAnimStart(className)
    this.initData()

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

Obtenemos el ** className correspondiente, ** pasamos el tipo correspondiente y ejecutamos el evento de inicialización correspondiente,  y la animación de la cámara se realiza mediante la función moveCameraAnim que hemos definido  .

El pseudocódigo relevante es el siguiente:

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

     this.moveCameraAnim(gv, config)
}

Resumen


Este proyecto nos dio una mejor comprensión de la energía eólica. Ya sea por las ventajas regionales del parque eólico, o por la estructura y el principio de funcionamiento de la turbina eólica.

Después de terminar este proyecto, he ganado mucho crecimiento y conocimiento. Una buena manera para el rápido crecimiento de la tecnología es elegir constantemente los detalles. El proyecto es una obra de arte, que debe pulirse continuamente hasta que sea satisfactorio. Cada punto sutil afectará el rendimiento detrás. Por lo tanto, debemos hacer cualquier cosa con el espíritu de un artesano.

Por supuesto, también espero que algunos socios tengan el coraje de explorar el campo de Internet industrial. Podemos lograr mucho más que eso. Esto requiere nuestra imaginación para agregar demostraciones más divertidas y prácticas a este campo. Y también puede aprender mucho conocimiento en la industria.

314 artículos originales publicados · Me gusta 138 · Visita 390,000+

Supongo que te gusta

Origin blog.csdn.net/u013161495/article/details/104276250
Recomendado
Clasificación