Newer
Older
robot_dog_patrol_front / src / components / threejs / init.js
/**
 * 初始化场景
 */
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as THREE from 'three'
import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader.js'

// 类声明
class SceneInit {
  /**
   * 构造函数,用来初始化
   * @param dom  dom
   * @param cameraObj
   */
  constructor(dom, cameraObj) {
    console.log('dom', dom)
    console.log('cameraObj', cameraObj)

    this.dom = dom // 创建dom属性
    this.groupObj = { // 创建groupObj属性
      gridHelperGroup: undefined,
    }
    // render前的回调函数集合
    this.animateCallBackArray = []
    this.init(cameraObj)
  }

  /**
   * 初始化
   */
  init(cameraObj) {
    // 初始化Renderer
    this.initRenderer()
    // 初始化摄像头
    this.initCamera(cameraObj)
    // 初始化场景
    this.initScene()
    // 初始化灯源
    this.initLight()
    // 摄像头控制器
    this.initControls()
    this.animateCallBackArray = []
    // 动画
    // setTimeout(() => {
    this.animate()
    this.resizeFun = this.updataWh.bind(this)
    window.addEventListener('resize', this.resizeFun)
  }

  /**
   * 初始化渲染器
   */
  initRenderer() {
    this.renderer = new THREE.WebGLRenderer({
      alpha: this.alpha, // 画布透明
      depthTest: true, // 深度测试
      antialias: true, // 是否开启抗锯齿效果,默认值为false。
      logarithmicDepthBuffer: true, // 对数深度缓存
    })
    // width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
    this.width = this.dom.clientWidth // 窗口文档显示区的宽度作为画布宽度
    this.height = this.dom.clientHeight // 窗口文档显示区的高度作为画布高度
    this.renderer.localClippingEnabled = true // 启用局部裁剪
    this.renderer.setSize(this.width, this.height) // 设置three.js渲染区域的尺寸(像素px)
    this.renderer.shadowMap.enabled = true // 如果遇到阴影贴图 必须渲染
    this.renderer.sortObjects = true // 用于控制three.js中物体的渲染顺序
    this.renderer.setPixelRatio(window.devicePixelRatio * 1) // 用于设置设备的像素比 通过设置设备像素比,可以优化高分辨率设备上的渲染效果,确保图形在不同分辨率的设备上都能清晰显示。
    this.renderer.domElement.style.position = 'absolute'
    this.renderer.domElement.style.top = '0px'
    this.renderer.domElement.style.left = '0px'
    // three.js执行渲染命令会输出一个canvas画布,也就是一个HTML元素,你可以插入到web页面中
    this.dom.appendChild(this.renderer.domElement)
    this.renderer.domElement.classList.add('threeCanvas') // 设置画布的class,非必须
    // this.renderer.outputEncoding = THREE.sRGBEncoding;
    this.renderer.gammaFactor = 2.2
    this.renderer.gammaOutPut = true
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap

    this.renderer.toneMappingExposure = 1.0
    // 物理灯光
    this.renderer.physicallyCorrectLights = true
  }

  /**
   * 初始化摄像头
   */

  initCamera(cameraObj) {
    if (!cameraObj) {
      cameraObj = {
        fov: 45, // 摄像机视锥体垂直视野角度。
        aspect: this.width / this.height, // 摄像机视锥体长宽比。
        near: 0.1, // 摄像机视锥体近端面。
        far: 100000, // 摄像机视锥体远端面。
        // position: [0, 50, 85],
        // position: [62, 67, 20],
        position: [0, 0, 12],
      }
    }
    this.camera = new THREE.PerspectiveCamera(
      cameraObj.fov,
      this.width / this.height, // 摄像机视锥体长宽比。
      //   cameraObj.aspect,
      cameraObj.near,
      cameraObj.far,
    )
    this.camera.position.set(...cameraObj.position)
    // this.camera.position.set([0, 50, 85])
  }

  /**
   * 初始化场景
   */
  initScene() {
    this.scene = new THREE.Scene()
  }

  /**
   * 初始化灯源
   */
  initLight() {
    // 默认环境光
    const ambientLight = new THREE.AmbientLight(0xFFFFFF) // 环境光
    this.scene.add(ambientLight)
    ambientLight.intensity = 0.3 // 光照强度
    const hemiLight = new THREE.HemisphereLight(0xFFFFBB, 0x080820, 1) // 模拟天空和地面的光照效果
    this.scene.add(hemiLight)
  }

  /**
   * 初始化控制器
   */
  initControls() {
    this.controller = new OrbitControls(this.camera, this.renderer.domElement)
  }

  /**
   *宽高变更时自动适配
   */
  updataWh() {
    this.width = this.dom.clientWidth
    this.height = this.dom.clientHeight
    this.camera.aspect = this.width / this.height
    this.camera.updateProjectionMatrix()
    this.renderer.setSize(this.width, this.height)
  }

  /**
   * 动画渲染
   * threejs可以借助HTML5的API请求动画帧window.requestAnimationFrame实现动画渲染。
   **/
  animate(time) {
    // 请求动画帧window.requestAnimationFrame
    // requestAnimationFrame实现周期性循环执行
    // requestAnimationFrame默认每秒钟执行60次,但不一定能做到,要看代码的性能
    this.animateKey = requestAnimationFrame(this.animate.bind(this))
    if (this.controller) {
      this.controller.update()
    }
    // 额外回调
    for (let i = 0; i < this.animateCallBackArray.length; i++) {
      if (this.animateCallBackArray[i]) {
        this.animateCallBackArray[i](time, this.renderer, this.scene)
      }
    }
    this.renders()
  }

  /**
   * 网格辅助线
   */
  grid() {
    // 网格的大小(宽度和高度) 它定义了从中心到边缘的距离,整个网格的实际尺寸是2 * size‌
    const size = 1000
    // 网格的划分数量,即网格线上有多少个分割点
    const divisions = 60
    // 主要颜色,默认为0x444444(深灰色),用于主轴线‌
    const colorMain = '0x444444'
    // 次要颜色,默认为0x888888(浅灰色),用于其他线‌
    const colorSecondary = '0x888888'
    this.groupObj.gridHelperGroup = new THREE.GridHelper(
      size,
      divisions,
      // colorMain,
      // colorSecondary,
    )
    this.scene.add(this.groupObj.gridHelperGroup)
  }

  /**
   * 渲染
   */
  renders() {
    if (this.renderer) {
      this.renderer.render(this.scene, this.camera)
    }
  }

  /**
   * 获取根对象,便于其它类使用公共场景变量
   */
  getRoot() {
    return {
      scene: this.scene,
      renderer: this.renderer,
      controller: this.controller,
      camera: this.camera,
      addAnimateCallBack: this.addAnimateCallBack,
      removeAnimateCallBack: this.removeAnimateCallBack,
    }
  }

  /**
   *
   * 更新前的回调,暂用于模型矩阵变化动画
   */
  addAnimateCallBack = (fun) => {
    const index = this.animateCallBackArray.length
    this.animateCallBackArray.push(fun)
    return index
  }

  /**
   *
   * 更新前的回调,暂用于模型矩阵变化动画
   */
  removeAnimateCallBack = (index) => {
    this.animateCallBackArray[index] = false
  }

  /**
   * 销毁,释放内存
   */
  destroy() {
    cancelAnimationFrame(this.animateKey)
    this.scene.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        if (child.material) {
          child.material.dispose && child.material.dispose()
        }
        if (child.geometry) {
          child.geometry && child.geometry.dispose()
        }

        !!child.clear && child.clear()
        child = null
      }
    })
    this.scene.environment = null
    this.scene.clear()
    const dom = this.renderer.domElement
    if (dom) {
      dom.parentNode.removeChild(dom)
    }
    this.renderer.renderLists.dispose()
    this.renderer.dispose()
    this.renderer.forceContextLoss()
    this.renderer.domElement = null
    this.renderer.content = null
    this.renderer = null
    window.removeEventListener('resize', this.resizeFun)
  }

  // 加载点云
  loadPCD(padPath) {
    const loader = new PCDLoader()
    loader.load(padPath, (points) => {
      this.scene.add(points) // 直接将点云添加到场景中
      this.animate()
    })
  }

  // 加载三维地图
  load3DMap() {
    // const tileMap = new TileMap({
    //   renderer: this.renderer,
    //   camera: this.camera,
    //   scene: this.scene,
    // })
    // tileMap.load()
  }
}

export default SceneInit