import XrFrame from 'XrFrame'; import BasicParticle from './SystemProperty/BasicParticle' import SubEmitter, { SubEmitterState } from './Util/SubEmitter' import ParticleInstance from './SystemProperty/ParticleInstance' import { BasicGradientMethod } from './Util/Gradient' const xrFrameSystem = wx.getXrFrameSystem(); const tempVec1 = xrFrameSystem.Vector3.createFromNumber(0, 0, 0); const tempVec2 = xrFrameSystem.Vector3.createFromNumber(0, 0, 0); function randomBetween(v1, v2, randomSeed = Math.random()) { if (v1 === v2) { return v1; } else { return randomSeed * Math.abs(v1 - v2) + Math.min(v1, v2); } }; // 继承了控制粒子系统渲染的基础类,这里将实现粒子运作的逻辑 export default class CustomParticle extends BasicParticle { public readonly priority: number = 300; public subEmitters = null; private _start: boolean = false; private _stop: boolean = false; private _alive: boolean = false; private _actualFrame: number = 0; private _excessInstance: number = 0; private _emitterWorldMatrix: XrFrame.Matrix4; private _emitterInverseWorldMatrix: XrFrame.Matrix4; private _tempEndColor: XrFrame.Vector4 = xrFrameSystem.Vector4.createFromNumber(0, 0, 0, 0); private _tempDiffColor: XrFrame.Vector4 = xrFrameSystem.Vector4.createFromNumber(0, 0, 0, 0); private _tempAccColorStep: XrFrame.Vector4 = xrFrameSystem.Vector4.createFromNumber(0, 0, 0, 0); private _activeSubEmitterSystem; private _rootEmitterSystem; get material() { return this._material; } set material(value: Material) { if (!this._mesh) { return; } } get id() { return this._mesh.id; } get data() { return this._data; } get particleEmitter() { return this._particleEmitter; } set data(value: IParticleData) { this._data = value; } /** * 粒子系统开始播放。 * * @param delay 设定粒子延时几秒后再播放。 */ public start(delay) { if (delay) { setTimeout(() => { this.start(0); }, delay); return; } this._start = true; this._stop = false; if (this._preWarmCycles) { for (var index = 0; index < this._preWarmCycles; index++) { this._updateRenderData(0, true); } } } /** * 停止粒子系统与其子发射器的播放。 */ public stop() { if (this._stop) { return; } this._stop = true; this.stopSubEmitters(); } /** * 当粒子系统添加到场景中时会执行的函数 */ public onAdd(parent: Element, data: IParticleData) { if (this._mesh) { return; } // 将元素和粒子所在场景给予对应成员变量 this.particleEl = this.el; this.particleScene = this.el.scene; this._data = data; this.initParticle(this._data); // 检查是否存在子发射器 this._prepareSubEmitterArray(); // 粒子系统开始运行,delay控制几秒后执行 this.start(this._delay); } /** * 初始化粒子系统的状态。 */ public initParticle(data: IParticleData) { this._systemId = CustomParticle.count++; this._parseProperties(data); this._parseAttribute(); this._createVertexBuffers(); this._createIndexBuffer(); this._registerGeometry(); this._createMesh(); this._chooseEmitterProcess(); this._setMeshData(this._material, data.uniforms, data.states); } /** * 获取一个粒子子发射器。 */ public createSubEmitter(data: IParticleData) { var particleSystem = new CustomParticle(); var tempData: IParticleData = {}; particleSystem.data = tempData; particleSystem.particleEl = this.particleEl || this.el; particleSystem.particleScene = this.particleScene || this.el.scene; for (var key in data) { particleSystem.data[key] = data[key]; } //此时还未解析后加入的properties数据, 当此subEmitter被clone后成员数据更新生效 var subEmitter = new SubEmitter(particleSystem) return subEmitter; } /** * 获取一个拷贝的粒子系统。 */ public clone() { var cloneParticleSystem = new CustomParticle(); var tempData: IParticleData = {}; for (var key in this._data) { tempData[key] = this._data[key] } cloneParticleSystem.data = tempData; cloneParticleSystem.particleEl = this.particleScene.createElement(xrFrameSystem.XRNode, { position: "0 0 0" }); this.particleEl.addChild(cloneParticleSystem.particleEl); cloneParticleSystem.particleScene = this.particleScene; return cloneParticleSystem; } /** * 重置粒子系统的状态。 */ public resetParticle() { this._stockInstances.length = 0; this._instances.length = 0; // canvas must be redrawed to make right phenomenon this._updateRenderData(0); this._setMeshData(this._material) }; /** * 检测子发射器的类型 */ protected _prepareSubEmitterArray() { this._subEmitters = []; if (this.subEmitters) { this.subEmitters.forEach((subEmitter) => { if (subEmitter instanceof CustomParticle) { this._subEmitters.push([new SubEmitter(subEmitter)]); } else if (subEmitter instanceof SubEmitter) { this._subEmitters.push([subEmitter]); } else if (subEmitter instanceof Array) { this._subEmitters.push(subEmitter); } }) } if (this._subEmitters && this._subEmitters.length != 0) { this._activeSubEmitterSystem = new Array(); } } /** * 停止所有粒子子系统的发射状态。 */ protected stopSubEmitters() { if (!this._activeSubEmitterSystem) { return; } this._activeSubEmitterSystem.forEach((subEmitterSystem) => { subEmitterSystem.stop(); }) this._activeSubEmitterSystem = new Array(); } /** * 粒子子发射系统从依附的粒子系统中剥离。 */ protected removeFromRoot() { if (!this._rootEmitterSystem) { return; } var index = this._rootEmitterSystem._activeSubEmitterSystem.indexOf(this); if (index !== -1) { this._rootEmitterSystem._activeSubEmitterSystem.splice(index, 1); } this._rootEmitterSystem = null; } /** * 每一帧粒子系统更新的逻辑 */ public onTick(deltaTime: number, data: IParticleData): void { this._updateSpeed = deltaTime / 1000; //layout发生变化时,会重置粒子系统 if (this._vertexLayoutDirty) { this.onUpdate(data, null); } else { this._updateRenderData(deltaTime); this._setMeshData(this._material, data.uniforms); } this._material?._checkTextures(deltaTime); } public onUpdate(data: IParticleData, preData: IParticleData) { if (!this._mesh) { return; } // 如果顶点布局发生变化,则需要重置粒子系统 if (this._vertexLayoutDirty) { this.resetParticle(); this._parseAttribute(); this._createVertexBuffers(); this._setMeshData(this._material, data.uniforms); this._vertexLayoutDirty = false; } else { //wxml属性更新 this.resetParticle(); this.initParticle(data); } } // 每一帧进行粒子生成和粒子位置与轨迹运算的核心逻辑 protected _updateRenderData(deltaTime: number, isPreWarm: boolean = false) { if (!this._start) { return; } var newInstanceSum; var rate = this._emitRate; var _scaledUpdateSpeed = this._updateSpeed * (isPreWarm ? this._preWarmStepOffset : 1); newInstanceSum = (rate * _scaledUpdateSpeed) >> 0; this._excessInstance += rate * _scaledUpdateSpeed - newInstanceSum; if (this._excessInstance > 1.0) { newInstanceSum += this._excessInstance >> 0; this._excessInstance -= this._excessInstance >> 0; } //burst mode //控制粒子喷射效果的逻辑 if (this._burstCount > 0) { var cycle: boolean = false; var begin: boolean = false; //达到喷射时间时,开始第一次喷射 if (this._burstCountTime < this._burstTime) { this._burstCountTime += this._updateSpeed; } else { begin = true; } if (begin) { // 判断喷射间隙, 如果喷射间隙过短则视为仅喷射一次 if (this._burstInterval < 0.01) { if (this._burstInterval >= 0) { newInstanceSum += this._burstCount; this._burstInterval = -1; } } else { this._burstCountInterval += this._updateSpeed; if (this._burstCountInterval > this._burstInterval) { cycle = true; this._burstCountInterval = 0 } } } // 判断喷射循环的次数 if (cycle) { if (this._burstCycle != -1) { if (this._burstCountCycle >= this._burstCycle >> 0) { cycle = false; } else { this._burstCountCycle++; } } } if (cycle) { newInstanceSum += this._burstCount >> 0; } } this._alive = false; if (!this._stop) { this._actualFrame += _scaledUpdateSpeed; if (this._stopDuration && this._actualFrame >= this._stopDuration) { this.stop(); } } else { newInstanceSum = 0; } this.update(newInstanceSum); if (this._stop) { if (!this._alive) { this._start = false; this.resetParticle(); this.removeFromRoot(); } } if (!isPreWarm) { var offset = 0; // 每帧此处需要清空_vertexData this._vertexData.fill(0); for (var index = 0; index < this._instances.length; index++) { var particle = this._instances[index]; this._appendParticleVertices(offset, particle); if (this._useRenderMesh) { offset += this._vertexCount; } else { offset += 4; } if (particle.subEmitterMuster && particle.subEmitterMuster.length > 0) { particle.subEmitterMuster.forEach((subEmitter: SubEmitter) => { subEmitter.particleSystem.onTick(deltaTime, subEmitter.particleSystem.data); }) } } if (this._activeSubEmitterSystem && this._activeSubEmitterSystem.length > 0) { this._activeSubEmitterSystem.forEach((particleSystem) => { particleSystem.onTick(deltaTime, particleSystem.data); }) } } } /** * 创建一个粒子实例。 */ protected createParticle() { var instance: ParticleInstance; if (this._stockInstances.length !== 0) { instance = this._stockInstances.pop(); instance.reset(); } else { instance = new ParticleInstance(this); } if (this._subEmitters && this._subEmitters.length > 0) { var subEmitters = this._subEmitters[Math.floor(Math.random() * this._subEmitters.length)]; instance.subEmitterMuster = []; subEmitters.forEach((subEmitter) => { if (subEmitter.state == SubEmitterState.ATTACH) { var tempEmitter = subEmitter.clone(); instance.subEmitterMuster.push(tempEmitter); tempEmitter.particleSystem.start(); } }) } return instance; }; /** * 启动处于END状态的粒子子发射器。 * @param {ParticleInstance} instance 粒子实例 */ protected particleSubEmitter(instance: ParticleInstance) { if (!this._subEmitters || this._subEmitters.length == 0) { return; } var tempIndex = Math.floor(Math.random() * this._subEmitters.length); this._subEmitters[tempIndex].forEach((subEmitter) => { if (subEmitter.state == SubEmitterState.END) { var tempEmitter = subEmitter.clone(); tempEmitter.particleSystem._rootEmitterSystem = this; // the position of sub emitter from one particle tempEmitter.particleSystem.emitterPosition = instance.position.clone(); this._activeSubEmitterSystem.push(tempEmitter.particleSystem); tempEmitter.particleSystem.start(); } }) } /** * 回收当前粒子实例,并放入储备粒子队列。 * @param {ParticleInstance} particle 粒子实例 */ protected recycleParticle(particle: ParticleInstance) { var lastParticle = this._instances.pop(); if (lastParticle !== particle) { lastParticle.copyTo(particle); } this._stockInstances.push(lastParticle); } /** * 更新每一个粒子的状态。 * @param {number} instancesSum 新生成的粒子数 */ protected update(instancesSum: number) { // Update current this._alive = this._instances.length > 0; if (!this._alive && this._stop) { return; } var emitterPosition = this._emitterPosition; this._emitterWorldMatrix = xrFrameSystem.Matrix4.IDENTITY.translate(emitterPosition.x, emitterPosition.y, emitterPosition.z); this._emitterWorldMatrix.inverse(this._emitterInverseWorldMatrix); var instance: ParticleInstance; var loop = (index) => { if (this._instances.length === this._capacity) { return "break"; } instance = this.createParticle(); this._instances.push(instance); this.initInstanceProperty(instance); }; for (var index = 0; index < instancesSum; index++) { var state = loop(index); if (state === "break") break; } this.updateInstanceProperty(this._instances); }; /** * 初始化粒子实例。 * @param {ParticleInstance} instance 需要初始化的粒子实例 */ protected initInstanceProperty(instance: ParticleInstance) { instance.lifeTime = randomBetween(this._minLifeTime, this._maxLifeTime); //Ramp if (this._useRampGradients) { instance.rampPos = xrFrameSystem.Vector4.createFromNumber(0, 1, 0, 1); } //Position this._particleEmitter.startPosition(this._emitterWorldMatrix, instance.position); //Rotation instance.angularSpeed = randomBetween(this._minAngularSpeed, this._maxAngularSpeed); instance.angle = randomBetween(this._startAngle, this._startAngle2); //Direction instance.speed = randomBetween(this._minSpeed, this._maxSpeed); this._particleEmitter.startDirection(this._emitterWorldMatrix, instance.direction, instance.position); //Color if (this._colorGradients && this._colorGradients.length > 0) { instance.currentColorGradient = this._colorGradients[0]; instance.currentColorGradient.getColor(instance.currentColor); instance.color = instance.currentColor.clone(); if (this._colorGradients.length > 1) { this._colorGradients[1].getColor(instance.currentColor2); } else { instance.currentColor2 = instance.currentColor.clone(); } } else { var lerpStep = Math.random(); var startColor2, endColor; startColor2 = this._startColor2 ?? this._startColor; endColor = this._endColor ?? this._startColor; this.lerpNumberArrayToVector(instance.color, this._startColor, startColor2, lerpStep); this.lerpNumberArrayToVector(this._tempEndColor, endColor, endColor, lerpStep); this._tempEndColor.sub(instance.color, this._tempDiffColor); this._tempDiffColor.scale(1 / instance.lifeTime, instance.colorStep); } //Color-alpha if (this._alphaGradients && this._alphaGradients.length > 0) { instance.currentAlphaGradient = this._alphaGradients[0]; instance.currentAlpha = instance.currentAlphaGradient.getFactor(); instance.color.w = instance.currentAlpha; if (this._alphaGradients.length > 1) { instance.currentAlpha2 = this._alphaGradients[1].getFactor(); } else { instance.currentAlpha2 = instance.currentAlpha; } } //Speed if (this._speedScaleGradients && this._speedScaleGradients.length > 0) { instance.currentSpeedScaleGradient = this._speedScaleGradients[0]; instance.currentSpeedScale = instance.currentSpeedScaleGradient.getFactor(); if (this._speedScaleGradients.length > 1) { instance.currentSpeedScale2 = this._speedScaleGradients[1].getFactor(); } else { instance.currentSpeedScale2 = instance.currentSpeedScale; } } //Limit-speed if (this._limitSpeedGradients && this._limitSpeedGradients.length > 0) { instance.currentLimitSpeedGradient = this._limitSpeedGradients[0]; instance.currentLimitSpeed = instance.currentLimitSpeedGradient.getFactor(); if (this._limitSpeedGradients.length > 1) { instance.currentLimitSpeed2 = this._limitSpeedGradients[1].getFactor(); } else { instance.currentLimitSpeed2 = instance.currentLimitSpeed; } } //Drag if (this._dragGradients && this._dragGradients.length > 0) { instance.currentDragGradient = this._dragGradients[0]; instance.currentDrag = instance.currentDragGradient.getFactor(); if (this._dragGradients.length > 1) { instance.currentDrag2 = this._dragGradients[1].getFactor(); } else { instance.currentDrag2 = instance.currentDrag; } } //Scale instance.scale.setValue(randomBetween(this._minScaleX, this._maxScaleX), randomBetween(this._minScaleY, this._maxScaleY)); if (this._sizeGradients && this._sizeGradients.length > 0) { instance.currentSizeGradient = this._sizeGradients[0]; instance.currentSize = instance.currentSizeGradient.getFactor(); instance.sizeGradientFactor = instance.currentSize; instance.startSize = randomBetween(this._minSize, this._maxSize); instance.size = instance.startSize * instance.sizeGradientFactor; if (this._sizeGradients.length > 1) { instance.currentSize2 = this._sizeGradients[1].getFactor(); } else { instance.currentSize2 = instance.currentSize; } } else { instance.size = randomBetween(this._minSize, this._maxSize); } //SpriteSheet if (this.useSpriteSheet) { instance.startSpriteCellIndex = this._startSpriteCellIndex; instance.endSpriteCellIndex = this._endSpriteCellIndex; } } protected fetch(u, v, width, height, content) { u = u * 0.5 + 0.5; v = v * 0.5 + 0.5; const wrappedU = (u * width) % width | 0; const wrappedV = (v * height) % height | 0; const position = (wrappedU + wrappedV * width) * 4; return content[position] / 255; } /** * 更新运动过程中粒子实例的各项属性以及子发射器状态。 * @param {Array} instances 粒子实例数组 */ protected updateInstanceProperty(instances) { var loop = (index) => { var instance: ParticleInstance = instances[index]; var scaledUpdateSpeed = this._updateSpeed; var previousAge = instance.age; instance.age += this._updateSpeed; if (instance.age > instance.lifeTime) { var diff = instance.age - previousAge; var oldDiff = instance.lifeTime - previousAge; scaledUpdateSpeed = (oldDiff * scaledUpdateSpeed) / diff; instance.age = instance.lifeTime; } this.processInstance(instance); if (this._particleEmitter.processInstance) { this._particleEmitter.processInstance(instance, this._updateSpeed); } // attached subemitter dynamic position if (instance.subEmitterMuster) { instance.subEmitterMuster.forEach((subEmitter) => { subEmitter.particleSystem.emitterPosition = instance.position.clone(); }) } if (this.useSpriteSheet) { instance.updateCellIndex(); } if (instance.age >= instance.lifeTime) { this.particleSubEmitter(instance); if (instance.subEmitterMuster) { instance.subEmitterMuster.forEach((subEmitter) => { subEmitter.particleSystem.stop(); }) instance.subEmitterMuster = null; } this.recycleParticle(instance); index--; } popIndex = index; } var popIndex; for (var index = 0; index < instances.length; index++) { loop(index); index = popIndex; } } /** * 更新粒子实例的各项属性。 * @param {ParticleInstance} instance 待更新的粒子实例 */ protected processInstance(instance: ParticleInstance) { var ratio = instance.age / instance.lifeTime; var scaledUpdateSpeed = this._updateSpeed; //RampColor if (this._useRampGradients) { if (this._colorRemapGradients && this._colorRemapGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._colorRemapGradients, (currentGradient, nextGradient, lerp) => { var min = currentGradient.factor + (nextGradient.factor - currentGradient.factor) * lerp; var max = currentGradient.factor2 + (nextGradient.factor2 - currentGradient.factor2) * lerp; instance.rampPos.x = min; instance.rampPos.y = max - min; }) } } //Color if (this._colorGradients && this._colorGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, lerp) => { if (instance.currentColorGradient != currentGradient) { instance.currentColor = instance.currentColor2.clone(); nextGradient.getColor(instance.currentColor2); instance.currentColorGradient = currentGradient; } instance.color.x = instance.currentColor.x + (instance.currentColor2.x - instance.currentColor.x) * lerp; instance.color.y = instance.currentColor.y + (instance.currentColor2.y - instance.currentColor.y) * lerp; instance.color.z = instance.currentColor.z + (instance.currentColor2.z - instance.currentColor.z) * lerp; instance.color.w = instance.currentColor.w + (instance.currentColor2.w - instance.currentColor.w) * lerp; }) } else { instance.colorStep.scale(scaledUpdateSpeed, this._tempAccColorStep); instance.color.add(this._tempAccColorStep, instance.color); if (instance.color.w < 0) { instance.color.w = 0; } if (instance.color.w > 1) { instance.color.w = 1; } } //Color Alpha if (this._alphaGradients && this._alphaGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._alphaGradients, (currentGradient, nextGradient, lerp) => { if (instance.currentAlphaGradient != currentGradient) { instance.currentAlpha = instance.currentAlpha2; instance.currentAlpha2 = nextGradient.getFactor(); instance.currentAlphaGradient = currentGradient; } instance.color.w = instance.currentAlpha + (instance.currentAlpha2 - instance.currentAlpha) * lerp; }) } //--- Velocity ---// var speed = instance.speed; //Speed if (this._speedScaleGradients && this._speedScaleGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._speedScaleGradients, (currentGradient, nextGradient, lerp) => { if (instance.currentSpeedScaleGradient != currentGradient) { instance.currentSpeedScale = instance.currentSpeedScale2; instance.currentSpeedScale = nextGradient.getFactor(); instance.currentSpeedScaleGradient = currentGradient; } var currentSpeedScale = instance.currentSpeedScale + (instance.currentSpeedScale2 - instance.currentSpeedScale) * lerp; speed = speed * currentSpeedScale; }) } //Limit-speed if (this._limitSpeedGradients && this._limitSpeedGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._limitSpeedGradients, (currentGradient, nextGradient, lerp) => { if (instance.currentLimitSpeedGradient != currentGradient) { instance.currentLimitSpeed = instance.currentSpeedScale2; instance.currentLimitSpeed = nextGradient.getFactor(); instance.currentLimitSpeedGradient = currentGradient; } var limitSpeed = instance.currentLimitSpeed + (instance.currentLimitSpeed2 - instance.currentLimitSpeed) * lerp; if (Math.abs(instance.speed) > limitSpeed) { speed = speed * (1 - this._speedDampenFactor); } }) } //Drag if (this._dragGradients && this._dragGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._dragGradients, (currentGradient, nextGradient, lerp) => { if (instance.currentDragGradient != currentGradient) { instance.currentDrag = instance.currentSpeedScale2; instance.currentDrag = nextGradient.getFactor(); instance.currentDragGradient = currentGradient; } instance.drag = instance.currentDrag + (instance.currentDrag2 - instance.currentDrag) * lerp; }) } var velocity = instance.direction.scale(speed * (1 - instance.drag)); //--- End Velocity ---// //Angle instance.angle += instance.angularSpeed * scaledUpdateSpeed; //Gravity var scaledGravityDirection = xrFrameSystem.Vector3.createFromNumber(0, -1 * this._gravity, 0); //Direction var scaledDirection = velocity.add(scaledGravityDirection).scale(scaledUpdateSpeed); //Position instance.position.add(scaledDirection, instance.position); //Size if (this._sizeGradients && this._sizeGradients.length > 0) { BasicGradientMethod.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, lerp) => { if (instance.currentSizeGradient != currentGradient) { instance.currentSize = instance.currentSize2; instance.currentSize2 = nextGradient.getFactor(); instance.currentSizeGradient = currentGradient; } instance.sizeGradientFactor = instance.currentSize + (instance.currentSize2 - instance.currentSize) * lerp; instance.size = instance.startSize * instance.sizeGradientFactor; }) } } } xrFrameSystem.registerComponent('custom-particle', CustomParticle)