Vue.js2+Cesium1.103.0 十一、Three.js 炸裂效果
ThreeModelBoom.vue
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
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>