Skip to content

路灯、白天黑夜

fps: 0
点击运行
<template>
  <div>
    <div class="flex space-between">
      <div>fps: {{ fps }}</div>
      <div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
    </div>
    <canvas v-if="isRunning" id="lightStreetLamp" class="stage"></canvas>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import 'babylonjs-loaders'
import {
  Engine,
  Scene,
  ArcRotateCamera,
  Vector3,
  Color3,
  MeshBuilder,
  StandardMaterial,
  CubeTexture,
  Texture,
  ImportMeshAsync,
  HemisphericLight,
  SpotLight
} from 'babylonjs'
import {
  AdvancedDynamicTexture,
  StackPanel,
  Control,
  TextBlock,
  Slider,
} from 'babylonjs-gui'

let sceneResources, adt

const fps = ref(0)
const isRunning = ref(false)

const onTrigger = async () => {
  if (!isRunning.value) {
    isRunning.value = true
    await nextTick()
    sceneResources = await initScene()
  } else {
    isRunning.value = false
    destroy()
  }
}

const initScene = async () => {
  const ele = document.getElementById("lightStreetLamp") as any

  ele.addEventListener('wheel', function(event) {
    // 根据需要处理滚动
    // 例如,可以修改相机的半径或角度
    event.preventDefault() // 阻止默认滚动行为
  })

  const engine: any = new Engine(ele, true, {
    preserveDrawingBuffer: true,
    stencil: true,
    disableWebGL2Support: false
  })

  const scene = new Scene(engine)
  scene.useRightHandedSystem = true

  const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
  camera.upperBetaLimit = Math.PI / 2.2
  camera.wheelPrecision = 30
  camera.panningSensibility = 10
  camera.attachControl(ele, true)
  camera.setPosition(new Vector3(20, 20, 20))

  const createLight = () => {
    const light = new HemisphericLight('light', new Vector3(1, 1, 0))
    light.intensity = 1
    return light
  }

  const createSkyBox = () => {
    const skyBox = MeshBuilder.CreateBox('skyBox', { size: 150 }, scene)
    const skyBoxMaterial = new StandardMaterial('skyBox', scene)
    skyBoxMaterial.backFaceCulling = false
    skyBoxMaterial.reflectionTexture = new CubeTexture('/images/skybox', scene)
    skyBoxMaterial.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE
    skyBoxMaterial.diffuseColor = new Color3(0, 0, 0)
    skyBoxMaterial.specularColor = new Color3(0, 0, 0)
    skyBox.material = skyBoxMaterial
  }

  const createGui = async (light, bulbInfo) => {
    adt = AdvancedDynamicTexture.CreateFullscreenUI('UI')
    const panel = new StackPanel()
    panel.width = '220px'
    panel.top = '-20px'
    panel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
    panel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
    adt.addControl(panel)

    const header = new TextBlock()
    header.text = '← 黑夜 白天 →'
    header.height = '30px'
    header.color = 'red'
    panel.addControl(header)

    const slider = new Slider()
    slider.minimum = 0
    slider.maximum = 1
    slider.borderColor = 'green'
    slider.color = 'blue'
    slider.background = 'yellow'
    slider.value = 1
    slider.height = '20px'
    slider.width = '200px'

    const [lampLight, bulb] = bulbInfo
    slider.onValueChangedObservable.add(value => {
      if (light) {
        light.intensity = value
      }
      if (lampLight) {
        lampLight.intensity = 1 - value

        const startColor = BABYLON.Color3.White()
        const endColor = BABYLON.Color3.Blue()
        bulb.material.emissiveColor = Color3.Lerp(startColor, endColor, 1 - value)
      }
    })
    panel.addControl(slider)
  }

  const createLamp = async () => {
    const loaderResult =  await ImportMeshAsync('/scenes/lamp.babylon', scene)
    const bulb: any = loaderResult.meshes.find(v => v.name === 'bulb')
    // const bulb = scene.getMeshByName('bulb')
    bulb.material.emissiveColor = Color3.White()
    const lampLight = new SpotLight(
      'lampLight', 
      Vector3.Zero(),
      new Vector3(0, -1, 0),
       0.8 * Math.PI, 0.01, 
       scene
    )
    lampLight.intensity = 0
    lampLight.diffuse = Color3.Red()
    lampLight.parent = bulb

    const lamp: any = loaderResult.meshes.find(v => v.name === 'lamp')
    // const lamp = scene.getMeshByName('lamp')
    lamp.position = new Vector3(2, 0, 2)
    lamp.rotation = Vector3.Zero()
    lamp.rotation.y = -Math.PI / 4
    const lamp1 = lamp.clone('lamp1')
    lamp1.position.x = -8
    lamp1.position.z = 1.2
    lamp1.rotation.y = Math.PI / 2
    const lamp2 = lamp1.clone('lamp2')
    lamp2.position.x = -2.7
    lamp2.position.z = 0.8
    lamp2.rotation.y = -Math.PI / 2
    const lamp3 = lamp.clone('lamp3')
    lamp3.position.z = -8

    return [lampLight, bulb]
  }

  const createVillage = async () => {
    await ImportMeshAsync('/scenes/valleyvillage.glb', scene)
  }

  const runAnimate = () => {
    engine.runRenderLoop(function() {
      if (scene && scene.activeCamera) {
        scene.render()

        fps.value = engine.getFps().toFixed(2)
      }
    })
  }

  const light = createLight()
  const bulbInfo = await createLamp()
  createGui(light, bulbInfo)
  createSkyBox()
  createVillage()
  runAnimate()

  return {
    scene,
    engine, 
  }
}

const destroy = () => {
  if (sceneResources) {
    sceneResources.engine.stopRenderLoop() 
    sceneResources.engine.dispose()
    sceneResources.scene.dispose()
    sceneResources = null
  }
  if (adt) {
    adt.dispose()
    adt = null
  }
}

onMounted(async() => {
  await nextTick()
})

onUnmounted(() => {
  destroy()
})
</script>