import * as THREE from 'three' ; import * as R from 'ramda' ; import { ModelData } from './modelDataParser' ; import { MeshRenderData } from './modelRenderer' ; /**
 * Calculates frame index by animation time, fps and frames number
 */ const getCurrentFrame = ( time , fps , numFrames ) => Math . floor ( ( time % ( numFrames / fps ) ) / ( 1 / fps ) ) ; /**
 * Returns the mesh's animation clips
 */ export const prepareAnimationClips = ( meshData , modelData ) : THREE . AnimationClip [ ] => meshData . geometryBuffers . map ( ( sequenceBuffers , i ) => { const sequence = modelData . sequences [ i ] ; const isNoLoop = false ; const frameBuffers = sequenceBuffers . map ( ( bufferAttribute , i ) => ( { name : i . toString ( ) , vertices : bufferAttribute . array } ) ) ; return THREE . AnimationClip . CreateFromMorphTargetSequence ( sequence . label , frameBuffers as any , sequence . fps , isNoLoop ) ; } ) ; /**
 * Creates mesh controller
 */ export const createMeshController = ( mesh , meshRenderData , modelData , isDefaultVisible = false ) => { mesh . visible = isDefaultVisible ; let activeAction ; let previousAction ; const framesSum = modelData . sequences . reduce ( ( acc , item ) => acc + item . numFrames , 0 ) ; mesh . morphTargetInfluences = Array ( framesSum ) . fill ( 0 ) ; const animationClips = prepareAnimationClips ( meshRenderData , modelData ) ; const mixer = new THREE . AnimationMixer ( mesh ) ; const actions = animationClips . map ( actionClip => mixer . clipAction ( actionClip , mesh ) ) ; const meshModelController = { /** Return pause state of the mesh */ get isPaused ( ) { if ( activeAction ) { return activeAction . paused ; } return false ; } , param rate setPlaybackRate : ( rate ) => { mixer . timeScale = rate !== 0 ? 1 / rate : 0 ; } , /**
         * Sets visibility of the mesh
         */ setVisibility : ( isVisible ) => { mesh . visible = isVisible ; } , /**
         * Updates delta tile
         */ update : ( deltaTime ) => mixer . update ( deltaTime ) , /**
         * Sets animation to play
         */ setAnimation : ( sequenceIndex ) => { const wasPaused = activeAction ? activeAction . paused : false ; previousAction = activeAction ; activeAction = actions [ sequenceIndex ] ; if ( previousAction ) { previousAction . stop ( ) ; } const geometry = mesh . geometry ; if ( geometry instanceof THREE . BufferGeometry ) { geometry . morphAttributes . position = meshRenderData . geometryBuffers [ sequenceIndex ] ; } activeAction . reset ( ) . play ( ) ; activeAction . paused = wasPaused ; } , /**
         * Set pause state of the running animation
         */ setPause : ( isPaused ) => { if ( activeAction ) { activeAction . paused = isPaused ; } } , /**
         * Jump to specific time of the running animation
         */ setTime : ( time ) => { if ( activeAction ) { activeAction . time = time ; } } , /**
         * Returns current time of the running animation
         */ getCurrentTime : ( ) => ( activeAction ? activeAction . time : 0 ) } ; return meshModelController ; } ; /**
 * The model state
 */ export type ModelState = { isPaused : boolean ; activeAnimationIndex : number ; showedSubModels : number [ ] ; frame : number ; playbackRate : number ; } ; todo export const createModelController = ( meshes , meshesRenderData , modelData , initialSequence = 0 ) => { let playbackRate = 1 ; let isAnimationPaused = false ; let activeSequenceIndex = initialSequence ; let showedSubModels = modelData . bodyParts . map ( ( ) => 0 ) ; const meshControllers = meshes . map ( ( bodyPart , bodyPartIndex ) => bodyPart . map ( ( subModel , subModelIndex ) => subModel . map ( ( mesh , meshIndex ) => createMeshController ( mesh , meshesRenderData [ bodyPartIndex ] [ subModelIndex ] [ meshIndex ] , modelData , subModelIndex === 0 ) ) ) ) ; meshControllers . forEach ( bodyPart => bodyPart . forEach ( subModel => subModel . forEach ( controller => controller . setAnimation ( activeSequenceIndex ) ) ) ) ; const getAnimationTime = ( ) => R . converge ( R . divide , [ R . sum , R . length ] ) ( meshControllers . reduce ( ( acc , bodyPart ) => acc . concat ( bodyPart . reduce ( ( acc , subModel ) => acc . concat ( subModel . map ( controller => controller . getCurrentTime ( ) ) ) , [ ] as number [ ] ) ) , [ ] as number [ ] ) ) ; const areSubModelsPaused = ( ) => meshControllers . reduce ( ( acc , bodyPart ) => acc && bodyPart . reduce ( ( acc , subModel ) => acc && subModel . reduce ( ( acc , controller ) => acc && controller . isPaused , true ) , true ) , true ) ; /** Returns current state of the model */ const getCurrentState = ( ) : ModelState => ( { isPaused : areSubModelsPaused ( ) , activeAnimationIndex : activeSequenceIndex , showedSubModels , frame : getCurrentFrame ( getAnimationTime ( ) , modelData . sequences [ activeSequenceIndex ] . fps , modelData . sequences [ activeSequenceIndex ] . numFrames ) , playbackRate } ) ; const modelController = { param deltaTime update : ( deltaTime ) => meshControllers . forEach ( bodyPart => bodyPart . forEach ( subModel => subModel . forEach ( controller => { controller . update ( deltaTime ) ; } ) ) ) , /** Returns current state of the model */ getCurrentState , /** Set pause state of the model */ setPause : ( isPaused ) => { isAnimationPaused = isPaused ; meshControllers . forEach ( bodyPart => bodyPart . forEach ( subModel => subModel . forEach ( controller => controller . setPause ( isAnimationPaused ) ) ) ) ; return getCurrentState ( ) ; } , param rate setPlaybackRate : ( rate ) => { playbackRate = rate ; meshControllers . forEach ( bodyPart => bodyPart . forEach ( subModel => subModel . forEach ( controller => controller . setPlaybackRate ( playbackRate ) ) ) ) ; return getCurrentState ( ) ; } , param sequenceIndex setAnimation : ( sequenceIndex ) => { activeSequenceIndex = sequenceIndex ; meshControllers . forEach ( bodyPart => bodyPart . forEach ( subModel => subModel . forEach ( controller => controller . setAnimation ( sequenceIndex ) ) ) ) ; return getCurrentState ( ) ; } , /**
         * Shows specified sub model
         */ showSubModel : ( bodyPartIndex , subModelIndex ) => { showedSubModels [ bodyPartIndex ] = subModelIndex ; meshControllers [ bodyPartIndex ] . forEach ( ( subModel , i ) => { const isVisible = i === subModelIndex ; subModel . forEach ( controller => { controller . setVisibility ( isVisible ) ; } ) ; } ) ; return getCurrentState ( ) ; } , /**
         * Sets specific frame of the running animations
         */ setFrame : ( frame ) => { const { numFrames , fps } = modelData . sequences [ activeSequenceIndex ] ; const safeFrame = R . clamp ( 0 , numFrames , frame ) ; const duration = numFrames / fps ; const specifiedTime = ( duration / numFrames ) * safeFrame ; meshControllers . forEach ( bodyPart => bodyPart . forEach ( subModel => subModel . forEach ( controller => controller . setTime ( specifiedTime ) ) ) ) ; return getCurrentState ( ) ; } } ; return modelController ; } ; export type ModelController = ReturnType < typeof createModelController > ;