import { vec3 , quat } from 'gl-matrix' ; import ModelInstance from '../../modelinstance' ; import { createSkeletalNodes , SkeletalNode } from '../../skeletalnode' ; import DataTexture from '../../gl/datatexture' ; import Texture from '../../texture' ; import MdxNode from './node' ; import AttachmentInstance from './attachmentinstance' ; import ParticleEmitter from './particleemitter' ; import ParticleEmitter2 from './particleemitter2' ; import RibbonEmitter from './ribbonemitter' ; import EventObjectEmitter from './eventobjectemitter' ; import EventObjectSpnEmitter from './eventobjectspnemitter' ; import EventObjectSplEmitter from './eventobjectsplemitter' ; import EventObjectUbrEmitter from './eventobjectubremitter' ; import EventObjectSndEmitter from './eventobjectsndemitter' ; import MdxModel from './model' ; import GenericObject from './genericobject' ; import { EMITTER_PARTICLE2_TEXTURE_OFFSET , EMITTER_EVENT_TEXTURE_OFFSET } from './geometryemitterfuncs' ; import Bounds from '../../bounds' ; const visibilityHeap = new Float32Array ( 1 ) ; const translationHeap = vec3 . create ( ) ; const rotationHeap = quat . create ( ) ; const scaleHeap = vec3 . create ( ) ; const colorHeap = new Float32Array ( 3 ) ; const alphaHeap = new Float32Array ( 1 ) ; const textureIdHeap = new Uint32Array ( 1 ) ; type SkeletalNodeObject = AttachmentInstance | ParticleEmitter | ParticleEmitter2 | RibbonEmitter | EventObjectEmitter ; /**
 * An MDX model instance.
 */ export default class MdxModelInstance extends ModelInstance { attachments : AttachmentInstance [ ] = [ ] ; particleEmitters : ParticleEmitter [ ] = [ ] ; particleEmitters2 : ParticleEmitter2 [ ] = [ ] ; ribbonEmitters : RibbonEmitter [ ] = [ ] ; eventObjectEmitters : ( EventObjectSpnEmitter | EventObjectSplEmitter | EventObjectUbrEmitter | EventObjectSndEmitter ) [ ] = [ ] ; nodes : SkeletalNode [ ] = [ ] ; sortedNodes : SkeletalNode [ ] = [ ] ; frame = 0 ; counter = 0 ; sequence = - 1 ; sequenceLoopMode = 0 ; sequenceEnded = false ; teamColor = 0 ; vertexColor = new Float32Array ( [ 1 , 1 , 1 , 1 ] ) ; allowParticleSpawn = false ; forced = true ; geosetColors : Float32Array [ ] = [ ] ; layerAlphas : number [ ] = [ ] ; layerTextures : number [ ] = [ ] ; uvAnims : Float32Array [ ] = [ ] ; worldMatrices : Float32Array | null = null ; boneTexture : DataTexture | null = null ; constructor ( model ) { super ( model ) ; for ( let i = 0 , l = model . geosets . length ; i < l ; i ++ ) { this . geosetColors [ i ] = new Float32Array ( 4 ) ; } for ( let i = 0 , l = model . layers . length ; i < l ; i ++ ) { this . layerAlphas [ i ] = 0 ; this . layerTextures [ i ] = 0 ; this . uvAnims [ i ] = new Float32Array ( 5 ) ; } const sharedNodeData = createSkeletalNodes ( model . genericObjects . length , MdxNode ) ; const nodes = sharedNodeData . nodes ; let nodeIndex = 0 ; this . nodes . push ( ... nodes ) ; this . worldMatrices = sharedNodeData . worldMatrices ; for ( const bone of model . bones ) { this . initNode ( nodes , nodes [ nodeIndex ++ ] , bone ) ; } for ( const light of model . lights ) { this . initNode ( nodes , nodes [ nodeIndex ++ ] , light ) ; } for ( const helper of model . helpers ) { this . initNode ( nodes , nodes [ nodeIndex ++ ] , helper ) ; } for ( const attachment of model . attachments ) { let attachmentInstance ; if ( attachment . internalModel ) { attachmentInstance = new AttachmentInstance ( this , attachment ) ; this . attachments . push ( attachmentInstance ) ; } this . initNode ( nodes , nodes [ nodeIndex ++ ] , attachment , attachmentInstance ) ; } for ( const emitterObject of model . particleEmitters ) { const emitter = new ParticleEmitter ( this , emitterObject ) ; this . particleEmitters . push ( emitter ) ; this . initNode ( nodes , nodes [ nodeIndex ++ ] , emitterObject , emitter ) ; } for ( const emitterObject of model . particleEmitters2 ) { const emitter = new ParticleEmitter2 ( this , emitterObject ) ; this . particleEmitters2 . push ( emitter ) ; this . initNode ( nodes , nodes [ nodeIndex ++ ] , emitterObject , emitter ) ; } for ( const emitterObject of model . ribbonEmitters ) { const emitter = new RibbonEmitter ( this , emitterObject ) ; this . ribbonEmitters . push ( emitter ) ; this . initNode ( nodes , nodes [ nodeIndex ++ ] , emitterObject , emitter ) ; } for ( const emitterObject of model . eventObjects ) { const type = emitterObject . type ; let emitter ; if ( type === 'SPN' ) { emitter = new EventObjectSpnEmitter ( this , emitterObject ) ; } else if ( type === 'SPL' ) { emitter = new EventObjectSplEmitter ( this , emitterObject ) ; } else if ( type === 'UBR' ) { emitter = new EventObjectUbrEmitter ( this , emitterObject ) ; } else { emitter = new EventObjectSndEmitter ( this , emitterObject ) ; } this . eventObjectEmitters . push ( emitter ) ; this . initNode ( nodes , nodes [ nodeIndex ++ ] , emitterObject , emitter ) ; } for ( const collisionShape of model . collisionShapes ) { this . initNode ( nodes , nodes [ nodeIndex ++ ] , collisionShape ) ; } const hierarchy = model . hierarchy ; for ( let i = 0 , l = nodes . length ; i < l ; i ++ ) { this . sortedNodes [ i ] = nodes [ hierarchy [ i ] ] ; } if ( model . bones . length ) { this . boneTexture = new DataTexture ( model . viewer . gl , 4 , model . bones . length * 4 , 1 ) ; } } /**
     * Override the texture at the given index.
     *
     * If a texture isn't given, removes the override if there was one.
     */ setTexture ( index , texture ? ) { this . overrideTexture ( index , texture ) ; } /**
     * Override the texture of the particle emitter the given index.
     *
     * If a texture isn't given, removes the override if there was one.
     */ setParticle2Texture ( index , texture ? ) { this . overrideTexture ( EMITTER_PARTICLE2_TEXTURE_OFFSET + index , texture ) ; } /**
     * Override the texture of the event emitter the given index.
     *
     * If a texture isn't given, removes the override if there was one.
     */ setEventTexture ( index , texture ? ) { this . overrideTexture ( EMITTER_EVENT_TEXTURE_OFFSET + index , texture ) ; } /**
     * Clear all of the emitted objects that belong to this instance.
     */ override clearEmittedObjects ( ) { for ( const emitter of this . particleEmitters ) { emitter . clear ( ) ; } for ( const emitter of this . particleEmitters2 ) { emitter . clear ( ) ; } for ( const emitter of this . ribbonEmitters ) { emitter . clear ( ) ; } for ( const emitter of this . eventObjectEmitters ) { emitter . clear ( ) ; } } /**
     * Initialize a skeletal node.
     */ initNode ( nodes , node , genericObject , object ? ) { vec3 . copy ( node . pivot , genericObject . pivot ) ; if ( genericObject . parentId === - 1 ) { node . parent = this ; } else { node . parent = nodes [ genericObject . parentId ] ; } node . dontInheritTranslation = genericObject . dontInheritTranslation ; node . dontInheritRotation = genericObject . dontInheritRotation ; node . dontInheritScaling = genericObject . dontInheritScaling ; if ( genericObject . billboarded ) { node . billboarded = true ; } else if ( genericObject . billboardedX ) { node . billboardedX = true ; } else if ( genericObject . billboardedY ) { node . billboardedY = true ; } else if ( genericObject . billboardedZ ) { node . billboardedZ = true ; } if ( object ) { node . object = object ; } } /**
     * Overriden to hide also attachment models.
     */ override hide ( ) { super . hide ( ) ; this . resetAttachments ( ) ; } /**
     * Updates all of this instance internal nodes and objects.
     * Nodes that are determined to not be visible will not be updated, nor will any of their children down the hierarchy.
     */ updateNodes ( dt , forced ) { const sequence = this . sequence ; const frame = this . frame ; const counter = this . counter ; const sortedNodes = this . sortedNodes ; const model = < MdxModel > this . model ; const sortedGenericObjects = model . sortedGenericObjects ; for ( let i = 0 , l = sortedNodes . length ; i < l ; i ++ ) { const genericObject = sortedGenericObjects [ i ] ; const node = sortedNodes [ i ] ; const parent = < MdxNode | SkeletalNode > node . parent ; let wasDirty = forced || parent . wasDirty || genericObject . anyBillboarding ; const variants = genericObject . variants ; if ( forced || variants [ 'generic' ] [ sequence ] ) { wasDirty = true ; if ( forced || variants [ 'translation' ] [ sequence ] ) { genericObject . getTranslation ( node . localLocation , sequence , frame , counter ) ; } if ( forced || variants [ 'rotation' ] [ sequence ] ) { genericObject . getRotation ( node . localRotation , sequence , frame , counter ) ; } if ( forced || variants [ 'scale' ] [ sequence ] ) { genericObject . getScale ( node . localScale , sequence , frame , counter ) ; } } node . wasDirty = wasDirty ; if ( wasDirty ) { node . recalculateTransformation ( this ) ; } if ( node . object ) { genericObject . getVisibility ( visibilityHeap , sequence , frame , counter ) ; if ( visibilityHeap [ 0 ] > 0 ) { ( < SkeletalNodeObject > node . object ) . update ( dt ) ; } } for ( const child of node . children ) { if ( wasDirty ) { child . recalculateTransformation ( ) ; } child . update ( dt ) ; } } } /**
     * If a model has no sequences or is running no sequence, it will only update once since it will never be forced to update.
     * This is generally the desired behavior, except when it is moved by the client.
     * Therefore, if an instance is transformed, always do a forced update.
     */ override recalculateTransformation ( ) { super . recalculateTransformation ( ) ; this . forced = true ; } /**
     * Update the batch data.
     */ updateBatches ( forced ) { const sequence = this . sequence ; const frame = this . frame ; const counter = this . counter ; const model = < MdxModel > this . model ; const geosets = model . geosets ; const layers = model . layers ; const geosetColors = this . geosetColors ; const layerAlphas = this . layerAlphas ; const layerTextures = this . layerTextures ; const uvAnims = this . uvAnims ; for ( let i = 0 , l = geosets . length ; i < l ; i ++ ) { const geoset = geosets [ i ] ; const geosetAnimation = geoset . geosetAnimation ; const geosetColor = geosetColors [ i ] ; if ( geosetAnimation ) { if ( forced || geosetAnimation . variants [ 'color' ] [ sequence ] ) { geosetAnimation . getColor ( colorHeap , sequence , frame , counter ) ; geosetColor [ 0 ] = colorHeap [ 0 ] ; geosetColor [ 1 ] = colorHeap [ 1 ] ; geosetColor [ 2 ] = colorHeap [ 2 ] ; } if ( forced || geosetAnimation . variants [ 'alpha' ] [ sequence ] ) { geosetAnimation . getAlpha ( alphaHeap , sequence , frame , counter ) ; geosetColor [ 3 ] = alphaHeap [ 0 ] ; } } else if ( forced ) { geosetColor [ 0 ] = 1 ; geosetColor [ 1 ] = 1 ; geosetColor [ 2 ] = 1 ; geosetColor [ 3 ] = 1 ; } } for ( let i = 0 , l = layers . length ; i < l ; i ++ ) { const layer = layers [ i ] ; const textureAnimation = layer . textureAnimation ; const uvAnim = uvAnims [ i ] ; if ( forced || layer . variants [ 'alpha' ] [ sequence ] ) { layer . getAlpha ( alphaHeap , sequence , frame , counter ) ; layerAlphas [ i ] = alphaHeap [ 0 ] ; } if ( forced || layer . variants [ 'textureId' ] [ sequence ] ) { layer . getTextureId ( textureIdHeap , sequence , frame , counter ) ; layerTextures [ i ] = textureIdHeap [ 0 ] ; } if ( textureAnimation ) { if ( forced || textureAnimation . variants [ 'translation' ] [ sequence ] ) { textureAnimation . getTranslation ( < Float32Array > translationHeap , sequence , frame , counter ) ; uvAnim [ 0 ] = translationHeap [ 0 ] ; uvAnim [ 1 ] = translationHeap [ 1 ] ; } if ( forced || textureAnimation . variants [ 'rotation' ] [ sequence ] ) { textureAnimation . getRotation ( < Float32Array > rotationHeap , sequence , frame , counter ) ; uvAnim [ 2 ] = rotationHeap [ 2 ] ; uvAnim [ 3 ] = rotationHeap [ 3 ] ; } if ( forced || textureAnimation . variants [ 'scale' ] [ sequence ] ) { textureAnimation . getScale ( < Float32Array > scaleHeap , sequence , frame , counter ) ; uvAnim [ 4 ] = scaleHeap [ 0 ] ; } } else if ( forced ) { uvAnim [ 0 ] = 0 ; uvAnim [ 1 ] = 0 ; uvAnim [ 2 ] = 0 ; uvAnim [ 3 ] = 1 ; uvAnim [ 4 ] = 1 ; } } } updateBoneTexture ( ) { if ( this . boneTexture ) { this . boneTexture . bindAndUpdate ( < Float32Array > this . worldMatrices ) ; } } override renderOpaque ( ) { const model = < MdxModel > this . model ; for ( const group of model . opaqueGroups ) { group . render ( this ) ; } } override renderTranslucent ( ) { const model = < MdxModel > this . model ; for ( const group of model . translucentGroups ) { group . render ( this ) ; } } override updateAnimations ( dt ) { const model = < MdxModel > this . model ; const sequenceId = this . sequence ; if ( sequenceId !== - 1 ) { const sequence = model . sequences [ sequenceId ] ; const interval = sequence . interval ; const frameTime = dt * 1000 ; this . frame += frameTime ; this . counter += frameTime ; this . allowParticleSpawn = true ; if ( this . frame >= interval [ 1 ] ) { if ( this . sequenceLoopMode === 2 || ( this . sequenceLoopMode === 0 && sequence . nonLooping === 0 ) ) { this . frame = interval [ 0 ] ; this . resetEventEmitters ( ) ; } else { this . frame = interval [ 1 ] ; this . counter -= frameTime ; this . allowParticleSpawn = false ; } this . sequenceEnded = true ; } else { this . sequenceEnded = false ; } } const forced = this . forced ; if ( sequenceId !== - 1 || forced ) { this . updateNodes ( dt , forced ) ; this . updateBoneTexture ( ) ; this . updateBatches ( forced ) ; } this . forced = false ; } /**
     * Set the team color of this instance.
     */ setTeamColor ( id ) { this . teamColor = id ; return this ; } /**
     * Set the vertex color of this instance.
     */ setVertexColor ( color ) { this . vertexColor . set ( color ) ; return this ; } /**
     * Set the sequence of this instance.
     */ setSequence ( id ) { const model = < MdxModel > this . model ; const sequences = model . sequences ; this . sequence = id ; if ( id < 0 || id > sequences . length - 1 ) { this . sequence = - 1 ; this . frame = 0 ; this . allowParticleSpawn = false ; } else { this . frame = sequences [ id ] . interval [ 0 ] ; } this . resetEventEmitters ( ) ; this . resetAttachments ( ) ; this . forced = true ; return this ; } override getBounds ( ) { const model = < MdxModel > this . model ; if ( this . sequence === - 1 ) { return model . bounds ; } const bounds = model . sequences [ this . sequence ] . bounds ; if ( bounds . r === 0 ) { return model . bounds ; } return bounds ; } /**
     * Set the sequence loop mode.
     * 0 to never loop, 1 to loop based on the model, and 2 to always loop.
     */ setSequenceLoopMode ( mode ) { this . sequenceLoopMode = mode ; return this ; } /**
     * Get an attachment node.
     */ getAttachment ( id ) { const model = < MdxModel > this . model ; const attachment = model . attachments [ id ] ; if ( attachment ) { return this . nodes [ attachment . index ] ; } return ; } resetEventEmitters ( ) { for ( const emitter of this . eventObjectEmitters ) { emitter . lastValue = 0 ; } } resetAttachments ( ) { for ( const attachment of this . attachments ) { attachment . internalInstance . hide ( ) ; } } }