130 lines
3.8 KiB
TypeScript
130 lines
3.8 KiB
TypeScript
/**
|
|
* 模型爆炸展开
|
|
*/
|
|
import * as THREE from 'three';
|
|
import {useDispatchSignal} from "@/hooks";
|
|
|
|
interface IModelExplodeData {
|
|
// 爆炸方向
|
|
worldDir: THREE.Vector3;
|
|
// 爆炸距离:mesh中心点到爆炸中心的距离
|
|
worldDistance:THREE.Vector3;
|
|
// 原始坐标
|
|
originPosition:THREE.Vector3;
|
|
// mesh中心
|
|
meshCenter:THREE.Vector3;
|
|
// 爆炸中心
|
|
explodeCenter:THREE.Vector3;
|
|
}
|
|
|
|
class ModelExplode{
|
|
// 模型爆炸的展开数据
|
|
meshExplodeData = new Map<string,Map<string,IModelExplodeData>>();
|
|
|
|
// 已执行模型爆炸未还原的模型
|
|
unrestoredModel:THREE.Object3D[] = [];
|
|
|
|
constructor() {
|
|
}
|
|
|
|
/**
|
|
* 计算模型爆炸的展开数据
|
|
*/
|
|
computedExplodeData(model:THREE.Object3D):void{
|
|
if(!model) return;
|
|
|
|
// 计算模型中心
|
|
const modelBox = new THREE.Box3();
|
|
modelBox.setFromObject(model);
|
|
const explodeCenter = this.getWorldCenterPosition(modelBox);
|
|
|
|
const meshBox = new THREE.Box3();
|
|
|
|
const dataMap:Map<string,IModelExplodeData> = new Map();
|
|
model.traverse((child) => {
|
|
if(!child || !child.isMesh || child.isLine || child.isSprite) return;
|
|
|
|
meshBox.setFromObject(child);
|
|
const meshCenter = this.getWorldCenterPosition(meshBox);
|
|
const worldDistance = new THREE.Vector3().subVectors(meshCenter,explodeCenter)
|
|
const meshExplodeData:IModelExplodeData = {
|
|
worldDir: worldDistance.clone().normalize(),
|
|
worldDistance:worldDistance,
|
|
originPosition:child.getWorldPosition(new THREE.Vector3()),
|
|
meshCenter:meshCenter.clone(),
|
|
explodeCenter:explodeCenter.clone(),
|
|
}
|
|
|
|
dataMap.set(child.uuid,meshExplodeData);
|
|
})
|
|
|
|
this.meshExplodeData.set(model.uuid,dataMap);
|
|
}
|
|
|
|
getWorldCenterPosition(box:THREE.Box3,scalar = 0.5){
|
|
return new THREE.Vector3().addVectors(box.max,box.min).multiplyScalar(scalar);
|
|
}
|
|
|
|
explodeModel(model:THREE.Object3D,scalar:number = 0.5){
|
|
if(!this.meshExplodeData.has(model.uuid)){
|
|
this.computedExplodeData(model);
|
|
}
|
|
|
|
const dataMap = this.meshExplodeData.get(model.uuid);
|
|
if(!dataMap) return;
|
|
|
|
model.traverse((child) => {
|
|
if(!dataMap.has(child.uuid)) return;
|
|
|
|
const data = dataMap.get(child.uuid);
|
|
if(!data) return;
|
|
|
|
const distance = data.worldDir.clone().multiplyScalar(data.worldDistance.length() * scalar);
|
|
const offset = new THREE.Vector3().subVectors(data.meshCenter,data.originPosition);
|
|
const center = data.explodeCenter;
|
|
const newPosition = new THREE.Vector3().copy(center).add(distance).sub(offset);
|
|
const localPosition = child.parent?.worldToLocal(newPosition);
|
|
|
|
if(localPosition){
|
|
child.position.copy(localPosition);
|
|
}
|
|
})
|
|
|
|
useDispatchSignal("sceneGraphChanged");
|
|
|
|
this.unrestoredModel.push(model);
|
|
}
|
|
|
|
// 还原
|
|
restore(){
|
|
this.unrestoredModel.forEach(model=>{
|
|
const dataMap = this.meshExplodeData.get(model.uuid);
|
|
if(!dataMap) return;
|
|
|
|
model.traverse((child) => {
|
|
if(!dataMap.has(child.uuid)) return;
|
|
|
|
const data = dataMap.get(child.uuid);
|
|
if(!data) return;
|
|
|
|
const _originPosition = child.parent?.worldToLocal(data.originPosition);
|
|
|
|
if(_originPosition){
|
|
child.position.copy(_originPosition);
|
|
}
|
|
})
|
|
|
|
useDispatchSignal("sceneGraphChanged");
|
|
})
|
|
|
|
this.unrestoredModel = [];
|
|
|
|
this.clear();
|
|
}
|
|
|
|
clear(){
|
|
this.meshExplodeData.clear();
|
|
}
|
|
}
|
|
|
|
export {ModelExplode}; |