import { assertExists } from '@ulangi/assert' ; import { BaseTransformer } from '@ulangi/ulangi-common/core' ; import { AtomShellType } from '@ulangi/ulangi-common/enums' ; import { ObservableAtomPlayScreen , ObservableCommandList , ObservableMoveToCommand , ObservableParticle , Observer , } from '@ulangi/ulangi-observable' ; import * as _ from 'lodash' ; import { config } from '../../constants/config' ; export class AtomParticleDelegate { private observer : Observer ; private observableScreen : ObservableAtomPlayScreen ; public constructor ( observer , observableScreen ) { this . observer = observer ; this . observableScreen = observableScreen ; } public autoMoveParticlesOnOriginPositionChange ( callback ? ) { this . observer . reaction ( ( ) : { x : number ; y : number ; } => this . observableScreen . origin . position , ( ) : void => { this . spreadParticlesByIndices ( callback ) ; } ) ; } public spreadParticlesByIndices ( callback ? ) { const commandList = new ObservableCommandList ( ) ; this . observableScreen . particles . forEach ( ( particle ) : void => { const shell = assertExists ( this . observableScreen . shells . find ( ( currentShell ) : boolean => particle . shellType === currentShell . shellType ) , 'shell should not be null or undefined' ) ; const radius = shell . diameter / 2 ; const angle = this . rotateLeft90Degrees ( this . calculateAngleByIndex ( particle . index , this . getParticleCountByShell ( particle . shellType ) ) ) ; const transformedX = radius * Math . cos ( angle ) ; const transformedY = radius * Math . sin ( angle ) ; const position = BaseTransformer . reverseTransform ( this . observableScreen . origin . position , { x : transformedX , y : transformedY } ) ; const centerX = position . x - config . atom . particleSize / 2 ; const centerY = position . y - config . atom . particleSize / 2 ; const newPosition = { x : centerX , y : centerY } ; const command = new ObservableMoveToCommand ( newPosition , 'incompleted' ) ; particle . commandList . commands . push ( command ) ; commandList . commands . push ( command ) ; } ) ; if ( typeof callback !== 'undefined' ) { this . observer . when ( ( ) : boolean => commandList . areAllCompleted ( ) , callback ) ; } } public moveAllParticlesToOrigin ( callback ? ) { const commandList = new ObservableCommandList ( ) ; const originPosition = this . observableScreen . origin . position ; this . observableScreen . particles . forEach ( ( particle ) : void => { const command = new ObservableMoveToCommand ( { x : originPosition . x - config . atom . particleSize / 2 , y : originPosition . y - config . atom . particleSize / 2 , } ) ; commandList . commands . push ( command ) ; particle . commandList . commands . push ( command ) ; } ) ; if ( typeof callback !== 'undefined' ) { this . observer . when ( ( ) : boolean => commandList . areAllCompleted ( ) , callback ) ; } } public transferParticleToAnotherShell ( particle , newPosition , newShell , callback ? ) { const oldShellType = particle . shellType ; particle . shellType = newShell ; const particlesInNewShell = this . getParticlesInShell ( newShell ) ; particlesInNewShell . forEach ( ( tempParticle ) : void => { if ( tempParticle . id === particle . id ) { tempParticle . newPosition = newPosition ; } else { tempParticle . newPosition = tempParticle . position ; } } ) ; const particlesInOldShell = this . getParticlesInShell ( oldShellType ) ; particlesInOldShell . forEach ( ( tempParticle ) : void => { tempParticle . newPosition = tempParticle . position ; } ) ; this . refactorIndicesByNewPositions ( particlesInNewShell ) ; this . refactorIndicesByNewPositions ( particlesInOldShell ) ; this . spreadParticlesByIndices ( callback ) ; } public transferParticleToSameShell ( particle , newPosition , callback ? ) { const particlesInShell = this . getParticlesInShell ( particle . shellType ) ; particlesInShell . forEach ( ( tempParticle ) : void => { if ( tempParticle . id === particle . id ) { tempParticle . newPosition = newPosition ; } else { tempParticle . newPosition = tempParticle . position ; } } ) ; const indiceChanged = this . refactorIndicesByNewPositions ( particlesInShell ) ; if ( indiceChanged ) { this . spreadParticlesByIndices ( callback ) ; } else { this . spreadParticlesByIndices ( ) ; } } public changeParticleColors ( particles , color ) { particles . forEach ( ( particle ) : void => { particle . color = color ; } ) ; } public isMaxReached ( shellType ) { const shell = assertExists ( this . observableScreen . shells . find ( ( currentShell ) : boolean => currentShell . shellType === shellType ) , 'shell should not be null or undefined' ) ; const particlesInShell = this . observableScreen . particles . filter ( ( particle ) : boolean => particle . shellType === shellType ) ; return shell . max === particlesInShell . length ; } public getParticleByCharacter ( character ) { return assertExists ( this . observableScreen . particles . find ( ( particle ) : boolean => particle . character === character ) , 'particle should not be null or undefined' ) ; } public getParticlesInShell ( shellType , _particles ? ) { const particles = _particles || this . observableScreen . particles ; return particles . filter ( ( particle ) : boolean => particle . shellType === shellType ) ; } public reverseSortByIndex ( particles ) { particles . sort ( ( particle1 , particle2 ) : number => { if ( particle1 . index > particle2 . index ) { return - 1 ; } else if ( particle1 . index < particle2 . index ) { return 1 ; } else { return 0 ; } } ) ; } public areParticlesInOneShellOnly ( particles ) { const firstParticle = _ . first ( particles ) ; if ( typeof firstParticle === 'undefined' ) { return true ; } else { return _ . every ( particles , ( particle ) : boolean => { return particle . shellType === firstParticle . shellType ; } ) ; } } private getParticleCountByShell ( shellType ) { return this . observableScreen . particles . filter ( ( particle ) : boolean => shellType === particle . shellType ) . length ; } private refactorIndicesByNewPositions ( particles ) { particles . sort ( ( particle1 , particle2 ) : number => { const particle1_newPosition = assertExists ( particle1 . newPosition , "particle 1's new position should not be null or undefined" ) ; const particle2_newPosition = assertExists ( particle2 . newPosition , "particle 2's new position should not be null or undefined" ) ; const angle1 = this . rotateRight90Degrees ( this . calculateAngleByPosition ( particle1_newPosition ) ) ; const angle2 = this . rotateRight90Degrees ( this . calculateAngleByPosition ( particle2_newPosition ) ) ; if ( angle1 < angle2 ) { return - 1 ; } else if ( angle1 > angle2 ) { return 1 ; } else { return 0 ; } } ) ; let hasChanged = false ; particles . forEach ( ( particle , index ) : void => { if ( particle . index !== index ) { particle . index = index ; hasChanged = true ; } } ) ; return hasChanged ; } private calculateAngleByPosition ( position ) { const { x , y } = BaseTransformer . transformBase ( this . observableScreen . origin . position , position ) ; const angle = Math . atan2 ( y , x ) ; return angle < 0 ? angle + 2 * Math . PI : angle ; } private calculateAngleByIndex ( index , total ) { return ( index / total ) * 2 * Math . PI ; } private rotateLeft90Degrees ( angle ) { return ( angle + Math . PI / 2 ) % ( 2 * Math . PI ) ; } private rotateRight90Degrees ( angle ) { return ( angle - Math . PI / 2 + 2 * Math . PI ) % ( 2 * Math . PI ) ; } }