
效果是这样。具体实现思路就是需要准备四个数据:
一、整个模型的中心:explodedCenter
二、每个mesh网格模型的中心meshCenter,存在每个mesh的userData里面。
三、通过三维向量运算计算出运动的方向
四、计算出运动的距离。用方向乘以距离就是运动完成以后的位置。
<template>
<div>
<div class="testContainer" ref="container"></div>
<div style="position: absolute;top: 180px;left:80px">
<button @click="explosion(true)">爆炸</button>
<button @click="explosion(false)" style="margin-left: 4px;">组合</button>
</div>
</div>
</template>
<script>
import * as THREE from "three";
import TWEEN from "@tweenjs/tween.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
import * as ViewSkip from "../utils/viewSkip";
export default {
name: "ExplodedView",
data() {
return {
container: null,
scene: null,
camera: null,
renderer: null,
controls: null,
meshList: [],
obj: {
ratio: 2,
},
};
},
mounted() {
this.initThree();
},
methods: {
initThree() {
this.scene = new THREE.Scene(); //场景
this.scene.background = new THREE.Color(0xffffff);
// 实例化一个gui对象
const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = "0px";
gui.domElement.style.width = "300px";
// 相机设置
var width = window.innerWidth;
var height = window.innerHeight;
var k = width / height;
var s = 50;
//创建相机对象
this.camera = new THREE.OrthographicCamera(
-s * k,
s * k,
s,
-s,
1,
10000
);
this.camera.position.set(0, 0, 100);
this.camera.up = new THREE.Vector3(0, 0, 1);
// 添加光源
//环境光
this.scene.add(new THREE.AmbientLight(0x666666));
// 平行光
const light = new THREE.DirectionalLight(0xdfebff, 0.7);
// gui.add(light, "intensity", 0, 2.0).name("平行光强度");
gui.add(this.obj, "ratio", 1, 10).name("爆炸系数");
light.position.set(1, 1, 1);
light.position.multiplyScalar(1.3);
this.scene.add(light);
// Three.js三维坐标轴 三个坐标轴颜色RGB分别对应xyz轴
let axesHelper = new THREE.AxesHelper(11250);
this.scene.add(axesHelper);
// 渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true, //开启抗锯齿(否则加载的模型有锯齿--)
// alpha: true
});
this.renderer.setSize(width, height);
this.renderer.setPixelRatio(window.devicePixelRatio); //设置设备像素比率,防止Canvas画布输出模糊。
this.renderer.render(this.scene, this.camera); //执行渲染操作
// this.renderer.outputColorSpace = THREE.sRGBEncoding;
this.container = this.$refs.container;
this.container.appendChild(this.renderer.domElement);
this.controls = new OrbitControls(this.camera, this.renderer.domElement); //旋转、放大缩小
// 加载模型
const loader = new GLTFLoader();
var dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
const model = new THREE.Group();
loader.load("地下厂房结构模型.gltf", (gltf) => {
model.add(gltf.scene);
this.scene.add(model);
gltf.scene.traverse((obj) => {
if (obj.isMesh) {
this.meshList.push(obj);
}
});
this.initExplodeModel(gltf.scene);
const box = new THREE.Box3();
box.expandByObject(gltf.scene); //扩展此包围盒的边界,使得对象及其子对象在包围盒内,包括对象和子对象的世界坐标的变换。
ViewSkip.animateCameraBySphere(
box,
this.camera,
this.controls,
this.container
);
});
this.animate();
},
animate() {
requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
TWEEN.update();
},
// 初始化爆炸数据保存到每个mesh的userdata上
initExplodeModel(modelObject) {
let _this = this;
if (!modelObject) return;
// 计算模型中心
const explodeBox = new THREE.Box3();
explodeBox.setFromObject(modelObject);
const explodeCenter = this.getWorldCenterPosition(explodeBox); //爆炸中心的世界坐标
const meshBox = new THREE.Box3();
// 遍历整个模型,保存数据到userData上,以便爆炸函数使用
this.meshList.map((value) => {
meshBox.setFromObject(value);
const meshCenter = _this.getWorldCenterPosition(meshBox);
// 爆炸方向
value.userData.worldDir = new THREE.Vector3()
.subVectors(meshCenter, explodeCenter) //subVectors(a,b)将该向量设置为a - b。
.normalize(); //有方向的单位向量(爆炸方向)
// 爆炸距离 mesh中心点到爆炸中心点的距离
value.userData.worldDistance = new THREE.Vector3().subVectors(
meshCenter, //网格模型中心点
explodeCenter //整个模型中心点
); //将该向量设置为a - b
// mesh中心点
value.userData.meshCenter = meshCenter.clone();
value.userData.explodeCenter = explodeCenter.clone();
});
},
// 获取模型中心点
getWorldCenterPosition(box, scalar = 0.5) {
return new THREE.Vector3()
.addVectors(box.max, box.min) //addVectors(a,b)将该向量设置为a+b
.multiplyScalar(scalar); //将该向量与所传入的标量s进行相乘。
},
explosion(isExpot) {
this.meshList.map((value) => {
const distance = value.userData.worldDir
.clone()
.multiplyScalar(
value.userData.worldDistance.length().toFixed(2) * this.obj.ratio
); //将该向量与所传入的标量s进行相乘。
// .length 计算从(0, 0, 0) 到 (x, y, z)的欧几里得长度 (Euclidean length,即直线长度)
// value.position.add(distance);
let movePosition = new THREE.Vector3()
// .length 计算从(0, 0, 0) 到 (x, y, z)的欧几里得长度 (Euclidean length,即直线长度)
isExpot ? movePosition.addVectors(value.position.clone(), distance) : movePosition.subVectors(value.position.clone(), distance)
new TWEEN.Tween(value.position).to(movePosition, 200).start()
});
},
},
};
</script>
<style></style>
引入的viewSkip.js文件:作用是保证引入所有模型都自动计算出相机的位置保证模型的加载
/*
* @Author: ZhuPan zhupan@cngim.com
* @Date: 2024-06-25 17:02:23
* @LastEditors: ZhuPan zhupan@cngim.com
* @LastEditTime: 2024-06-25 17:41:22
* @FilePath: \threetest\src\utils\viewSkip.js
* @Description: 视角跳转的简单版本,用来测试
*/
import * as THREE from "three";
export function animateCameraBySphere(box, camera, controls, container) {
// 获取包围球
var sphere = new THREE.Sphere();
box.getBoundingSphere(sphere);
// 如果指定了target(sphere) ,结果将会被拷贝到target 获取一个包围球
// 计算相机最终位置
if (sphere.radius > 0) {
var line3 = new THREE.Line3(sphere.center, camera.position); //sphere.center线段的起始点 camera.position线段的终点
// Line3用起点和终点表示的几何线段。
var pos = new THREE.Vector3();
line3.at((sphere.radius * 2) / line3.distance(), pos);
// .at ( t : Float, target : Vector3 ) : Vector3
// t - 使用值0-1返回沿线段的位置。
// target — 计算结果会被拷贝到target。
// 返回一个线段某一位置的向量,当 t = 0的时候返回起始点,当t = 1的时候返回终点
// 解决钻孔局部视图超出范围
var size = new THREE.Vector3();
box.getSize(size);
// target — 如果指定了target ,结果将会被拷贝到target。
// 返回包围盒的宽度,高度,和深度。
var radius = sphere.radius;
if (size.z > sphere.radius * 1.6) {
radius = sphere.radius * 1.5;
}
// 相机参数
var k = container.offsetWidth / container.offsetHeight;
var left = -radius * 2;
var bottom = (-radius * 2) / k;
var far = radius * 8;
var near = 1;
var zoom = 1;
} else {
// 当box是一个点时
pos = camera.position;
left = camera.left;
bottom = camera.bottom;
far = camera.far;
near = 1;
zoom = 1;
}
camera.position.set(pos.x, pos.y, pos.z);
camera.bottom = bottom;
camera.top = -bottom;
camera.far = far;
camera.near = near;
camera.left = left;
camera.right = -left;
camera.zoom = zoom;
camera.updateProjectionMatrix();
controls.target.x = sphere.center.x;
controls.target.y = sphere.center.y;
controls.target.z = sphere.center.z;
controls.update();
}
4190

被折叠的 条评论
为什么被折叠?



