Files
La-Fabrik/src/utils/three/ExplodedModel.ts
T
2026-05-08 02:12:58 +01:00

111 lines
2.8 KiB
TypeScript

import * as THREE from "three";
export interface ExplodedPart {
object: THREE.Object3D;
originalPosition: THREE.Vector3;
targetPosition: THREE.Vector3;
}
interface ExplodedModelOptions {
distance?: number;
speed?: number;
}
const _center = new THREE.Vector3();
const _direction = new THREE.Vector3();
export class ExplodedModel {
private readonly parts: ExplodedPart[] = [];
private readonly distance: number;
private readonly speed: number;
private progress = 0;
private targetProgress = 0;
constructor(model: THREE.Object3D, options: ExplodedModelOptions = {}) {
this.distance = options.distance ?? 1.2;
this.speed = options.speed ?? 6;
this.parts = this.createParts(model);
}
setSplit(split: boolean): void {
this.targetProgress = split ? 1 : 0;
}
getParts(): readonly ExplodedPart[] {
return this.parts;
}
update(delta: number): void {
const diff = this.targetProgress - this.progress;
if (Math.abs(diff) < 0.001) {
this.progress = this.targetProgress;
} else {
this.progress += diff * Math.min(delta * this.speed, 1);
}
this.parts.forEach((part) => {
part.object.position.lerpVectors(
part.originalPosition,
part.targetPosition,
this.progress,
);
});
}
private createParts(model: THREE.Object3D): ExplodedPart[] {
const root =
model.children.length === 1 && model.children[0]
? model.children[0]
: model;
const directChildren = root.children.filter((child) => hasMesh(child));
const sourceObjects =
directChildren.length > 1 ? directChildren : getMeshes(root);
if (sourceObjects.length === 0) return [];
_center.set(0, 0, 0);
sourceObjects.forEach((object) => _center.add(object.position));
_center.divideScalar(sourceObjects.length);
return sourceObjects.map((object, index) => {
const originalPosition = object.position.clone();
_direction.subVectors(originalPosition, _center);
if (_direction.lengthSq() < 0.0001) {
const angle = (index / sourceObjects.length) * Math.PI * 2;
_direction.set(Math.cos(angle), 0.25, Math.sin(angle));
}
_direction.normalize();
return {
object,
originalPosition,
targetPosition: originalPosition
.clone()
.addScaledVector(_direction, this.distance),
};
});
}
}
function hasMesh(object: THREE.Object3D): boolean {
let found = false;
object.traverse((child) => {
if (child instanceof THREE.Mesh) {
found = true;
}
});
return found;
}
function getMeshes(object: THREE.Object3D): THREE.Object3D[] {
const meshes: THREE.Object3D[] = [];
object.traverse((child) => {
if (child instanceof THREE.Mesh) {
meshes.push(child);
}
});
return meshes;
}