threejs实现任意模型的爆炸效果

实现原理

  • 初始化数据:遍历计算每个mesh的中心点,保存在userData上
  • 爆炸计算逻辑:以当前中心的到原点的矢量A作为爆炸方向,|A| * d(爆炸系数 ) 作为爆炸距离;
  • 渲染:在循环渲染中每次更改 d ,这样就可以实现爆炸动画效果了
// 初始化爆炸数据保存到每个mesh的userdata上
function initExplodeModel(modelObject: THREE.Object3D) {
  if (!modelObject) return;

  // 计算模型中心
  const explodeBox = new THREE.Box3();
  explodeBox.setFromObject(modelObject);
  const explodeCenter = getWorldCenterPosition(explodeBox);

  const meshBox = new THREE.Box3();

  // 遍历整个模型,保存数据到userData上,以便爆炸函数使用
  modelObject.traverse(function (value: any) {
    if (value.isLine || value.isSprite) return;
    if (value.isMesh) {
      meshBox.setFromObject(value);

      const meshCenter = getWorldCenterPosition(meshBox);
      // 爆炸方向
      value.userData.worldDir = new THREE.Vector3()
        .subVectors(meshCenter, explodeCenter)
        .normalize();
      // 爆炸距离 mesh中心点到爆炸中心点的距离
      value.userData.worldDistance = new THREE.Vector3().subVectors(meshCenter, explodeCenter);
      // 原始坐标
      value.userData.originPosition = value.getWorldPosition(new THREE.Vector3());
      // mesh中心点
      value.userData.meshCenter = meshCenter.clone();
      value.userData.explodeCenter = explodeCenter.clone();
    }
  });
}
// 模型爆炸函数 
const explodeModel = (model: THREE.Object3D, scalar: number) => {
  model.traverse(function (value) {
    // @ts-ignore
    if (!value.isMesh || !value.userData.originPosition) return;
    const distance = value.userData.worldDir
      .clone()
      .multiplyScalar(value.userData.worldDistance.length() * scalar);
    const offset = new THREE.Vector3().subVectors(
      value.userData.meshCenter,
      value.userData.originPosition
    );
    const center = value.userData.explodeCenter;
    const newPos = new THREE.Vector3().copy(center).add(distance).sub(offset);
    const localPosition = value.parent?.worldToLocal(newPos.clone());
    localPosition && value.position.copy(localPosition);
  });
};

辅助代码

function getWorldCenterPosition(box: THREE.Box3, scalar = 0.5): THREE.Vector3 {
  return new THREE.Vector3().addVectors(box.max, box.min).multiplyScalar(scalar);
}

伪代码

	<Slider onChange={onChnage}/>

	import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
	const loader = new GLTFLoader();
    let obj= null
    loader.load('your-model-url', (model) => {
        obj = model.scene
        initExplodeModel(model.scene)
    })

    const onChnage = (val) => {
        explodeModel(obj, value) 
    }

完整代码

  • 视图层
<Slider onChange={onChnage}/>
  • 逻辑层
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 初始化爆炸数据保存到每个mesh的userdata上
function initExplodeModel(modelObject: THREE.Object3D) {
  if (!modelObject) return;

  // 计算模型中心
  const explodeBox = new THREE.Box3();
  explodeBox.setFromObject(modelObject);
  const explodeCenter = getWorldCenterPosition(explodeBox);

  const meshBox = new THREE.Box3();

  // 遍历整个模型,保存数据到userData上,以便爆炸函数使用
  modelObject.traverse(function (value: any) {
    if (value.isMark || value.isMarkChild || value.isLine || value.isSprite) return;
    if (value.isMesh) {
      meshBox.setFromObject(value);

      const meshCenter = getWorldCenterPosition(meshBox);
      // 爆炸方向
      value.userData.worldDir = new THREE.Vector3()
        .subVectors(meshCenter, explodeCenter)
        .normalize();
      // 爆炸距离 mesh中心点到爆炸中心点的距离
      value.userData.worldDistance = new THREE.Vector3().subVectors(meshCenter, explodeCenter);
      // 原始坐标
      value.userData.originPosition = value.getWorldPosition(new THREE.Vector3());
      // mesh中心点
      value.userData.meshCenter = meshCenter.clone();
      value.userData.explodeCenter = explodeCenter.clone();
    }
  });
}

function getWorldCenterPosition(box: THREE.Box3, scalar = 0.5): THREE.Vector3 {
  return new THREE.Vector3().addVectors(box.max, box.min).multiplyScalar(scalar);
}

function explodeModel(model: THREE.Object3D, scalar: number) {
  model.traverse(function (value) {
    // @ts-ignore
    if (!value.isMesh || !value.userData.originPosition) return;
    const distance = value.userData.worldDir
      .clone()
      .multiplyScalar(value.userData.worldDistance.length() * scalar);
    const offset = new THREE.Vector3().subVectors(
      value.userData.meshCenter,
      value.userData.originPosition
    );
    const center = value.userData.explodeCenter;
    const newPos = new THREE.Vector3().copy(center).add(distance).sub(offset);
    const localPosition = value.parent?.worldToLocal(newPos.clone());
    localPosition && value.position.copy(localPosition);
  });
};

let obj: any = null

const loader = new GLTFLoader()
loader.load('your-model-url', (model) => {
    obj = model.scene
    initExplodeModel(model.scene)
})

const onChnage = (val) => {
    explodeModel(obj, val) 
}

效果图

评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值