实现原理
- 初始化数据:遍历计算每个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)
}