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;
}
}
}