Skip to content

Smoke

Example

Example Source Code
vue
<template>
  <div style="height: 500px" class="vw-full vh-full">
    <div style="height: 400px">
      <mb-map
        :zoom="16"
        :center="mapCenter"
        :bearing="0"
        :pitch="45"
        @created="createdHandler"
      >
        <mb-tianditu-layer />
      </mb-map>
    </div>
  </div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import type { MapboxInstance } from '@mapbox-vue3/core'

const mapCenter = [116.3466, 39.8704]
let map: MapboxInstance

const createdHandler = (mapbox: MapboxInstance) => {
  map = mapbox
  loaderModels()
}
const loaderModels = async () => {
  const promiseStar = new Promise((resolve) => {
    new THREE.TextureLoader().load(
      `${__RESOURCE_URL__}images/smoke.png`,
      (texture) => {
        resolve(texture)
      }
    )
  })
  const [state, textureStar] = await promiseStar.then(
    (rs) => [true, rs, null],
    (err) => [false, null, err]
  )
  if (!state) return

  // 先创建一个空的缓冲几何体
  // First create an empty buffer geometry
  const geometry = new THREE.BufferGeometry()
  geometry.setAttribute(
    'position',
    new THREE.BufferAttribute(new Float32Array([]), 3)
  ) // 一个顶点由3个坐标构成 A vertex consists of 3 coordinates
  geometry.setAttribute(
    'a_opacity',
    new THREE.BufferAttribute(new Float32Array([]), 1)
  ) // 点的透明度,用1个浮点数表示 The transparency of the point, expressed as a floating point number
  geometry.setAttribute(
    'a_size',
    new THREE.BufferAttribute(new Float32Array([]), 1)
  ) // 点的初始大小,用1个浮点数表示 The initial size of the point, expressed as a floating point number
  geometry.setAttribute(
    'a_scale',
    new THREE.BufferAttribute(new Float32Array([]), 1)
  ) // 点的放大量,用1个浮点数表示 The amount of point magnification, expressed as a floating point number

  const material = new THREE.PointsMaterial({
    color: '#333',
    map: textureStar,
    transparent: true,
    depthWrite: false,
  })

  // 修正着色器 Correction shader
  material.onBeforeCompile = function (shader) {
    const vertexShaderAttribute = `
          attribute float a_opacity;
          attribute float a_size;
          attribute float a_scale;
          varying float v_opacity;
          void main() {
            v_opacity = a_opacity;
          `
    const vertexShaderSize = `
          gl_PointSize = a_size * a_scale;
          `
    shader.vertexShader = shader.vertexShader.replace(
      'void main() {',
      vertexShaderAttribute
    )
    shader.vertexShader = shader.vertexShader.replace(
      'gl_PointSize = size;',
      vertexShaderSize
    )

    const fragmentShaderVarying = `
          varying float v_opacity;
          void main() {
        `
    const fragmentShaderOpacity = `
          gl_FragColor = vec4( outgoingLight, diffuseColor.a * v_opacity );
        `
    shader.fragmentShader = shader.fragmentShader.replace(
      'void main() {',
      fragmentShaderVarying
    )
    shader.fragmentShader = shader.fragmentShader.replace(
      'gl_FragColor = vec4( outgoingLight, diffuseColor.a );',
      fragmentShaderOpacity
    )
  }

  // 创建点,并添加进场景 Create points and add them to the scene
  const points = new THREE.Points(geometry, material)
  const points2 = points.clone()

  points.rotation.z = Math.PI
  points2.rotation.z = Math.PI
  points2.rotation.x = -Math.PI / 4
  addThreeLayer(points, points2)

  // 定义Partical类 Defining Partial Classes
  class Partical {
    range: number
    center: Record<string, number>
    life: number
    createTime: number
    updateTime: number
    size: number
    opacityFactor: number
    opacity: number
    scaleFactor: number
    scale: number
    position: Record<string, number>
    speed: Record<string, number>
    constructor(range = 10, center = { x: 0, y: 0, z: 0 }) {
      this.range = range // 粒子的分布半径 Particle distribution radius
      this.center = center // 粒子的分布中心 The distribution center of particles
      this.life = 5000 // 粒子的存活时间,毫秒 The lifetime of the particle, in milliseconds
      this.createTime = Date.now() // 粒子创建时间 Particle creation time
      this.updateTime = Date.now() // 上次更新时间 Last updated
      this.size = 500 // 粒子大小 Particle size

      // 粒子透明度,及系数 粒子透明度,及系数
      this.opacityFactor = 1
      this.opacity = 1 * this.opacityFactor

      // 粒子放大量,及放大系数 Particle amplification and amplification factor
      this.scaleFactor = 3
      this.scale =
        1 + (this.scaleFactor * (this.updateTime - this.createTime)) / this.life // 初始1,到达生命周期时为3

      // 粒子位置 Particle Position
      this.position = {
        x: Math.random() * 2 * this.range + this.center.x - this.range,
        y: this.center.y,
        z: Math.random() * 2 * this.range + this.center.z - this.range,
      }

      // 水平方向的扩散 Horizontal diffusion
      let speedAround = Math.random() * 40
      if (speedAround < 20) speedAround -= 50
      if (speedAround > 20) speedAround += 10

      // 粒子的扩散速度 The diffusion rate of particles
      this.speed = {
        x: speedAround,
        y: Math.random() * 100 + 300,
        z: speedAround,
      }
    }

    update() {
      const now = Date.now()
      const time = now - this.updateTime

      this.position.x += (this.speed.x * time) / 1000
      this.position.y += (this.speed.y * time) / 1000
      this.position.z += (this.speed.z * time) / 1000

      // 计算粒子透明度 Calculating particle transparency
      this.opacity = 1 - (now - this.createTime) / this.life
      this.opacity *= this.opacityFactor
      if (this.opacity < 0) this.opacity = 0

      // 计算放大量 Calculation magnification
      this.scale = 1 + (this.scaleFactor * (now - this.createTime)) / this.life
      if (this.scale > 1 + this.scaleFactor) this.scale = 1 + this.scaleFactor

      // 重置更新时间 Reset Update Time
      this.updateTime = now
    }
  }

  let particals: Partical[] = []
  setInterval(() => {
    particals.push(new Partical(10, { x: 0, y: 100, z: 0 }))
  }, 500)

  // 校验粒子,并更新粒子位置等数据
  // Verify particles and update particle position and other data
  setInterval(() => {
    particals = particals.filter((partical) => {
      partical.update()
      if (partical.updateTime - partical.createTime > partical.life) {
        return false
      } else {
        return true
      }
    })
    if (!particals.length) return

    // 遍历粒子,收集属性 Traverse particles and collect properties
    const positionList: number[] = []
    const opacityList: number[] = []
    const scaleList: number[] = []
    const sizeList: number[] = []
    particals.forEach((partical) => {
      const { x, y, z } = partical.position
      positionList.push(x, y, z)
      opacityList.push(partical.opacity)
      scaleList.push(partical.scale)
      sizeList.push(partical.size)
    })
    // 粒子属性写入 Particle attribute writing
    geometry.setAttribute(
      'position',
      new THREE.BufferAttribute(new Float32Array(positionList), 3)
    )
    geometry.setAttribute(
      'a_opacity',
      new THREE.BufferAttribute(new Float32Array(opacityList), 1)
    )
    geometry.setAttribute(
      'a_scale',
      new THREE.BufferAttribute(new Float32Array(scaleList), 1)
    )
    geometry.setAttribute(
      'a_size',
      new THREE.BufferAttribute(new Float32Array(sizeList), 1)
    )
  }, 20)
}
const addThreeLayer = (points, points2) => {
  const threeLayer = new map.mapboxgl.supermap.ThreeLayer('threeLayer')
  threeLayer.on('initialized', render)

  let light: THREE.PointLight
  function render() {
    const renderer = threeLayer.getThreeRenderer(),
      scene = threeLayer.getScene(),
      camera = threeLayer.getCamera()

    light = new THREE.PointLight(0xffffff, 0.8)
    light.position.copy(camera.position)
    scene.add(light)
    scene.add(new THREE.AmbientLight(0xffffff))

    threeLayer.setPosition(points, mapCenter)
    threeLayer.setPosition(points2, [mapCenter[0] + 0.002, mapCenter[1]])

    scene.add(points)
    scene.add(points2)
    ;(function animate() {
      renderer.render(scene, camera)
      requestAnimationFrame(animate)
    })()
  }

  threeLayer.on('render', () => {
    light && light.position.copy(threeLayer.renderer.camera.position)
  })
  map.addLayer(threeLayer)
}
</script>