import * as THREE from "three"; import {gsap} from "gsap"; import {STATES} from "../config/constants.js"; export class SequenceController { constructor(main) { this.main = main; } startCollapse(u, v) { const {stateController, pixelVoxelMesh, physics, params, settings} = this.main; if (stateController.state === STATES.ANIMATING) return; stateController.changeState(STATES.ANIMATING); pixelVoxelMesh.setUniform("uRippleCenter", {x: u, y: v}); const aspect = this.main.gridInfo.cols / this.main.gridInfo.rows; const centerX = u * aspect; const centerY = v; const corners = [ {x: 0, y: 0}, {x: aspect, y: 0}, {x: 0, y: 1}, {x: aspect, y: 1}, ]; const maxDist = Math.max(...corners.map((c) => Math.hypot(c.x - centerX, c.y - centerY))); const targetVal = maxDist + params.effectSpread + 0.1; gsap.to(pixelVoxelMesh.material.uniforms.uVoxelateProgress, { value: targetVal, duration: params.baseDuration * targetVal, ease: "none", onComplete: () => { physics.forceActivateDrop((index) => { gsap.delayedCall(params.dropDelay, () => { physics.resetBodyParameters(index, settings.bodyDamping); }); }); stateController.changeState(STATES.COLLAPSED); }, }); } startReverse() { const {stateController, pixelVoxelMesh, physics, settings} = this.main; if (stateController.state !== STATES.COLLAPSED) return; stateController.changeState(STATES.REVERSING); const cachedData = physics.prepareForReverse(); const qTmp = new THREE.Quaternion(); const progressObj = {value: 0}; const staggerStrength = 2.0; gsap.to(progressObj, { value: 1 + staggerStrength, duration: 1, ease: "power2.inOut", onUpdate: () => { const globalT = progressObj.value; cachedData.forEach((data) => { let t = Math.max(0, Math.min(1, globalT - data.delay * staggerStrength)); if (t <= 0 || (t >= 1 && globalT < 1)) return; const pos = { x: data.startPos.x + (data.endPos.x - data.startPos.x) * t, y: data.startPos.y + (data.endPos.y - data.startPos.y) * t, z: data.startPos.z + (data.endPos.z - data.startPos.z) * t, }; qTmp.copy(data.startRot).slerp(data.endRot, t); data.rb.setTranslation(pos, true); data.rb.setRotation(qTmp, true); }); physics.syncMesh(pixelVoxelMesh); }, onComplete: () => { gsap.to(pixelVoxelMesh.material.uniforms.uVoxelateProgress, { value: 0.0, duration: 0.3, ease: "none", onComplete: () => { physics.setGravity(settings.gravityY); physics.resetDropFlags(); stateController.changeState(STATES.DEFAULT); }, }); }, }); } update() { const {stateController, pixelVoxelMesh, physics, params, gridInfo, settings} = this.main; if (!physics.dropFlags || !physics.cachedUVs) return; if (stateController.state === STATES.ANIMATING) { const currentProgress = pixelVoxelMesh.material.uniforms.uVoxelateProgress.value; if (currentProgress < 0.01) return; const rippleCenter = pixelVoxelMesh.material.uniforms.uRippleCenter.value; const aspectVecX = gridInfo.cols / gridInfo.rows; physics.checkAndActivateDrop(rippleCenter, currentProgress, aspectVecX, params.organicMode, params.effectSpread, gridInfo.spacing, gridInfo.count, (index) => { gsap.delayedCall(params.dropDelay, () => { physics.resetBodyParameters(index, settings.bodyDamping); }); }); } } reset() { if (this.main.pixelVoxelMesh) { gsap.killTweensOf(this.main.pixelVoxelMesh.material.uniforms.uVoxelateProgress); } gsap.getTweensOf("*").forEach((t) => t.kill()); } }