threejs爆炸图

该文章已生成可运行项目,

效果是这样。具体实现思路就是需要准备四个数据:

一、整个模型的中心: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();
}

本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值