Vue.js2+Cesium1.103.0 十一、Three.js 炸裂效果

Vue.js2+Cesium1.103.0 十一、Three.js 炸裂效果

Demo

ThreeModelBoom.vue

<template>
  <div
    :id="id"
    class="three_container"
  />
</template>

<script>
/* eslint-disable eqeqeq */
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
/* eslint-disable no-caller */
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
// import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
// import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
export default {
  name: 'ThreeModel',
  props: {
    size: {
      type: Number,
      default: 20
    },
    url: {
      type: String,
      default: 'three_container'
    },
    id: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      modelMixer: null,
      modelClock: null,
      modelAnimationAction: null,
      modelAnimationAction2: null,
      model: null,
      scene: null,
      camera: null,
      renderer: null,
      textureLoader: null,
      groupBox: null,
      control: null,
      enableRotate: null
    }
  },
  computed: {},
  watch: {},
  mounted() {
    window.cancelAnimationFrame(this.clearAnim)
    this.init()
  },
  beforeDestroy() {
    window.cancelAnimationFrame(this.clearAnim)
  },
  methods: {
    applyScalar(scalar) {
      if (!this.model) {
        return
      }
      this.model.traverse(function (value) {
        if (!value.isMesh || !value.worldDir) return
        // 爆炸公式
        value.position.copy(
          new THREE.Vector3()
            .copy(value.userData.oldPs)
            .add(
              new THREE.Vector3().copy(value.worldDir).multiplyScalar(scalar)
            )
        )
      })
    },
    async init() {
      const _this = this
      const element = document.getElementById(this.id)
      const width = element.clientWidth // 窗口宽度
      const height = element.clientHeight // 窗口高度
      // 场景
      this.scene = new THREE.Scene()
      // this.scene.background = new THREE.Color(0x000000, 0)
      this.scene.background = null

      // 相机
      const k = width / height // 窗口宽高比
      const s = 400 // 三维场景显示范围控制系数,系数越大,显示的范围越大
      // this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000) // 透视摄像机
      this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000) // 正交摄像机
      // 设置摄像机位置,相机方向逆X轴方向,倾斜向下看
      this.camera.position.set(0, 180, 360)
      // this.camera.rotation.order = 'YXZ'
      // 指向场景中心
      this.camera.lookAt(this.scene.position)

      // 渲染器
      this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
      // 设置环境
      this.renderer.setClearColor(0x000000, 0)
      // 设置场景大小
      this.renderer.setSize(800, 800)
      // 渲染器开启阴影效果
      this.renderer.shadowMap.enabled = true

      // 纹理加载器
      this.textureLoader = new THREE.TextureLoader()

      // 组合对象
      this.groupBox = new THREE.Group()

      // 坐标轴
      // const axes = new THREE.AxesHelper(1000)
      // this.scene.add(axes)

      // 点光源
      const point = new THREE.PointLight(0xffffff)
      point.position.set(500, 300, 400) // 点光源位置
      this.scene.add(point) // 点光源添加到场景中

      // 环境光
      const ambient = new THREE.AmbientLight(0xffffff, 0.8)
      this.scene.add(ambient)

      element.appendChild(this.renderer.domElement)

      // 相机控件
      this.control = new OrbitControls(this.camera, this.renderer.domElement)
      this.control.enableDamping = true
      // 动态阻尼系数 就是鼠标拖拽旋转灵敏度,阻尼越小越灵敏
      this.control.dampingFactor = 0.5
      // 是否可以缩放
      this.control.enableZoom = true
      // 是否自动旋转
      this.control.autoRotate = false
      // 设置相机距离原点的最近距离
      this.control.minDistance = 20
      // 设置相机距离原点的最远距离
      this.control.maxDistance = 1000
      // 是否开启右键拖拽
      this.control.enablePan = true
      // 上下翻转的最大角度
      this.control.maxPolarAngle = 1.5
      // 上下翻转的最小角度
      this.control.minPolarAngle = 0.0
      // 是否可以旋转
      this.enableRotate = true

      // 加载模型
      const loader = new GLTFLoader()
      await loader.load(
        this.url,
        gltf => {
          gltf.scene.name = 'Cesium_Air'
          gltf.scene.scale.set(_this.size, _this.size, _this.size) //  设置模型大小缩放
          gltf.scene.position.set(0, 0, 0)
          gltf.scene.translateY(0)
          _this.modelMixer = new THREE.AnimationMixer(gltf.scene)
          _this.modelClock = new THREE.Clock()
          if (gltf.animations.length > 0) {
            // http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/animation/AnimationAction
            _this.modelAnimationAction = _this.modelMixer.clipAction(
              gltf.animations[0]
            )
            _this.modelAnimationAction.timeScale = 1
            // _this.modelAnimationAction.loop = THREE.LoopOnce // 播放一次
            _this.modelAnimationAction.clampWhenFinished = true
          }
          _this.scene.add(gltf.scene)
          _this.model = gltf.scene

          // 模型包围盒
          const modelBox3 = new THREE.Box3()
          const meshBox3 = new THREE.Box3()

          // 获取模型的包围盒
          modelBox3.expandByObject(_this.model)
          // 计算模型的中心点坐标,这个为爆炸中心
          const modelWorldPs = new THREE.Vector3()
            .addVectors(modelBox3.max, modelBox3.min)
            .multiplyScalar(0.5)

          _this.model.traverse(function (value) {
            if (value.isMesh) {
              meshBox3.setFromObject(value)
              // 获取每个 mesh 的中心点,爆炸方向为爆炸中心点指向 mesh 中心点
              const worldPs = new THREE.Vector3()
                .addVectors(meshBox3.max, meshBox3.min)
                .multiplyScalar(0.5)
              if (isNaN(worldPs.x)) return
              // 计算爆炸方向
              value.worldDir = new THREE.Vector3()
                .subVectors(worldPs, modelWorldPs)
                .normalize()
              // 保存初始坐标
              value.userData.oldPs = value.getWorldPosition(new THREE.Vector3())
            }
          })
        },
        _xhr => {
          // console.log((_xhr.loaded / _xhr.total) * 100 + '% loaded')
        },
        _error => {
          // console.error(_error)
        }
      )

      const animate = () => {
        // 循环调用函数
        this.clearAnim = requestAnimationFrame(animate)
        // 更新相机控件
        this.control.update()
        // 渲染界面
        this.renderer.render(this.scene, this.camera)
        if (this.modelMixer) {
          // modelClock.getDelta() 方法获得两帧的时间间隔
          // 更新混合器相关的时间
          this.modelMixer.update(this.modelClock.getDelta())
        }
      }
      animate()
    }
  }
}
</script>


index.vue

<template>
  <div
    id="cesium-container"
    style="width: 100%; height: 100%;"
  >
    <div style="position: absolute;width: 400px;right: 50px;top: 100px;z-index: 9;">
      <div>
        <el-slider
          v-model="sliderVal"
          :min="0"
          :max="100"
          @input="handleChange"
        />
      </div>
    </div>
    <div class="model_container">
      <ThreeModel
        :id="'three_model_a'"
        ref="ThreeModelA"
        :url="'model/SnowyVillage.glb'"
        :size="5"
        class="three_model"
      />
    </div>
  </div>
</template>

<script>
import ThreeModel from './components/ThreeModelBoom.vue'

export default {
  components: {
    ThreeModel
  },
  data() {
    return {
      sliderVal: 0,
      paused: false
    }
  },
  computed: {},
  watch: {},
  mounted() {
    window.$InitMap()
  },
  methods: {
    handleChange(val) {
      this.$refs.ThreeModelA.applyScalar(val)
    }
  }
}
</script>

<style lang="scss">
.model_container {
  position: absolute;
  z-index: 999;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
  display: flex;
  .three_model {
    width: 800px;
    height: 800px;
  }
}
</style>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值