TkAstral3D/packages/sdk/lib/core/tools/ModelExplode.ts
2025-10-04 23:36:07 +08:00

130 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 模型爆炸展开
*/
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};