import { Vector3D } from '@awayjs/core'; import { ShaderRegisterCache, ShaderRegisterElement } from '@awayjs/stage'; import { ShaderBase, AnimationRegisterData } from '@awayjs/renderer'; import { ParticleProperties } from '../data/ParticleProperties'; import { ParticlePropertiesMode } from '../data/ParticlePropertiesMode'; import { ParticleSpriteSheetState } from '../states/ParticleSpriteSheetState'; import { ParticleAnimationSet } from '../ParticleAnimationSet'; import { AnimatorBase } from '../AnimatorBase'; import { ParticleNodeBase } from './ParticleNodeBase'; /** * A particle animation node used when a spritesheet texture is required to animate the particle. * NB: to enable use of this node, the repeat property on the material has to be set to true. */ export class ParticleSpriteSheetNode extends ParticleNodeBase { /** @private */ public _iUsesCycle: boolean; /** @private */ public _iUsesPhase: boolean; /** @private */ public _iTotalFrames: number; /** @private */ public _iNumColumns: number; /** @private */ public _iNumRows: number; /** @private */ public _iCycleDuration: number; /** @private */ public _iCyclePhase: number; /** * Reference for spritesheet node properties on a single particle (when in local property mode). * Expects a Vector3D representing the cycleDuration (x), optional phaseTime (y). */ public static UV_VECTOR3D: string = 'UVVector3D'; /** * Defines the number of columns in the spritesheet, when in global mode. Defaults to 1. Read only. */ public get numColumns(): number { return this._iNumColumns; } /** * Defines the number of rows in the spritesheet, when in global mode. Defaults to 1. Read only. */ public get numRows(): number { return this._iNumRows; } /** * Defines the total number of frames used by the spritesheet, when in global mode. Defaults to the number defined by numColumns and numRows. Read only. */ public get totalFrames(): number { return this._iTotalFrames; } /** * Creates a new ParticleSpriteSheetNode * * @param mode Defines whether the mode of operation acts on local properties of a particle or global properties of the node. * @param [optional] numColumns Defines the number of columns in the spritesheet, when in global mode. Defaults to 1. * @param [optional] numRows Defines the number of rows in the spritesheet, when in global mode. Defaults to 1. * @param [optional] cycleDuration Defines the default cycle duration in seconds, when in global mode. Defaults to 1. * @param [optional] cyclePhase Defines the default cycle phase, when in global mode. Defaults to 0. * @param [optional] totalFrames Defines the total number of frames used by the spritesheet, when in global mode. Defaults to the number defined by numColumns and numRows. * @param [optional] looping Defines whether the spritesheet animation is set to loop indefinitely. Defaults to true. */ constructor(mode: number, usesCycle: boolean, usesPhase: boolean, numColumns: number = 1, numRows: number = 1, cycleDuration: number = 1, cyclePhase: number = 0, totalFrames: number = Number.MAX_VALUE) { super('ParticleSpriteSheet', mode, usesCycle ? (usesPhase ? 3 : 2) : 1, ParticleAnimationSet.POST_PRIORITY + 1); this._pStateClass = ParticleSpriteSheetState; this._iUsesCycle = usesCycle; this._iUsesPhase = usesPhase; this._iNumColumns = numColumns; this._iNumRows = numRows; this._iCyclePhase = cyclePhase; this._iCycleDuration = cycleDuration; this._iTotalFrames = Math.min(totalFrames, numColumns * numRows); } /** * @inheritDoc */ public getAGALUVCode(shader: ShaderBase, animationSet: ParticleAnimationSet, registerCache: ShaderRegisterCache, animationRegisterData: AnimationRegisterData): string { //get 2 vc const uvParamConst1: ShaderRegisterElement = registerCache.getFreeVertexConstant(); const uvParamConst2: ShaderRegisterElement = (this._pMode == ParticlePropertiesMode.GLOBAL) ? registerCache.getFreeVertexConstant() : registerCache.getFreeVertexAttribute(); animationRegisterData.setRegisterIndex(this, ParticleSpriteSheetState.UV_INDEX_0, uvParamConst1.index); animationRegisterData.setRegisterIndex(this, ParticleSpriteSheetState.UV_INDEX_1, uvParamConst2.index); const uTotal: ShaderRegisterElement = new ShaderRegisterElement(uvParamConst1.regName, uvParamConst1.index, 0); const uStep: ShaderRegisterElement = new ShaderRegisterElement(uvParamConst1.regName, uvParamConst1.index, 1); const vStep: ShaderRegisterElement = new ShaderRegisterElement(uvParamConst1.regName, uvParamConst1.index, 2); const uSpeed: ShaderRegisterElement = new ShaderRegisterElement(uvParamConst2.regName, uvParamConst2.index, 0); const cycle: ShaderRegisterElement = new ShaderRegisterElement(uvParamConst2.regName, uvParamConst2.index, 1); const phaseTime: ShaderRegisterElement = new ShaderRegisterElement(uvParamConst2.regName, uvParamConst2.index, 2); let temp: ShaderRegisterElement = registerCache.getFreeVertexVectorTemp(); const time: ShaderRegisterElement = new ShaderRegisterElement(temp.regName, temp.index, 0); const vOffset: ShaderRegisterElement = new ShaderRegisterElement(temp.regName, temp.index, 1); temp = new ShaderRegisterElement(temp.regName, temp.index, 2); const temp2: ShaderRegisterElement = new ShaderRegisterElement(temp.regName, temp.index, 3); const u: ShaderRegisterElement = new ShaderRegisterElement(animationRegisterData.uvTarget.regName, animationRegisterData.uvTarget.index, 0); const v: ShaderRegisterElement = new ShaderRegisterElement(animationRegisterData.uvTarget.regName, animationRegisterData.uvTarget.index, 1); let code: string = ''; //scale uv code += 'mul ' + u + ',' + u + ',' + uStep + '\n'; if (this._iNumRows > 1) code += 'mul ' + v + ',' + v + ',' + vStep + '\n'; if (this._iUsesCycle) { if (this._iUsesPhase) code += 'add ' + time + ',' + animationRegisterData.vertexTime + ',' + phaseTime + '\n'; else code += 'mov ' + time + ',' + animationRegisterData.vertexTime + '\n'; code += 'div ' + time + ',' + time + ',' + cycle + '\n'; code += 'frc ' + time + ',' + time + '\n'; code += 'mul ' + time + ',' + time + ',' + cycle + '\n'; code += 'mul ' + temp + ',' + time + ',' + uSpeed + '\n'; } else code += 'mul ' + temp.toString() + ',' + animationRegisterData.vertexLife + ',' + uTotal + '\n'; if (this._iNumRows > 1) { code += 'frc ' + temp2 + ',' + temp + '\n'; code += 'sub ' + vOffset + ',' + temp + ',' + temp2 + '\n'; code += 'mul ' + vOffset + ',' + vOffset + ',' + vStep + '\n'; code += 'add ' + v + ',' + v + ',' + vOffset + '\n'; } code += 'div ' + temp2 + ',' + temp + ',' + uStep + '\n'; code += 'frc ' + temp + ',' + temp2 + '\n'; code += 'sub ' + temp2 + ',' + temp2 + ',' + temp + '\n'; code += 'mul ' + temp + ',' + temp2 + ',' + uStep + '\n'; if (this._iNumRows > 1) code += 'frc ' + temp + ',' + temp + '\n'; code += 'add ' + u + ',' + u + ',' + temp + '\n'; return code; } /** * @inheritDoc */ public getAnimationState(animator: AnimatorBase): ParticleSpriteSheetState { return animator.getAnimationState(this); } /** * @inheritDoc */ public _iProcessAnimationSetting(particleAnimationSet: ParticleAnimationSet): void { particleAnimationSet.hasUVNode = true; } /** * @inheritDoc */ public _iGeneratePropertyOfOneParticle(param: ParticleProperties): void { if (this._iUsesCycle) { const uvCycle: Vector3D = param[ParticleSpriteSheetNode.UV_VECTOR3D]; if (!uvCycle) throw (new Error('there is no ' + ParticleSpriteSheetNode.UV_VECTOR3D + ' in param!')); if (uvCycle.x <= 0) throw (new Error('the cycle duration must be greater than zero')); const uTotal: number = this._iTotalFrames / this._iNumColumns; this._pOneData[0] = uTotal / uvCycle.x; this._pOneData[1] = uvCycle.x; if (this._iUsesPhase) this._pOneData[2] = uvCycle.y; } } }