mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2024-12-26 11:48:29 +00:00
461 lines
16 KiB
JavaScript
461 lines
16 KiB
JavaScript
/****************************************************************************
|
|
Copyright (c) 2018 Xiamen Yaji Software Co., Ltd.
|
|
|
|
https://www.cocos.com/
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated engine source code (the "Software"), a limited,
|
|
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
|
to use Cocos Creator solely to develop games on your target platforms. You shall
|
|
not use Cocos Creator software for developing other software or tools that's
|
|
used for developing games. You are not granted to publish, distribute,
|
|
sublicense, and/or sell copies of Cocos Creator.
|
|
|
|
The software or tools in this License Agreement are licensed, not sold.
|
|
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
const js = require('../core/platform/js');
|
|
const misc = require('../core/utils/misc');
|
|
|
|
const ZERO_VEC2 = cc.v2(0, 0);
|
|
let _pos = cc.v2();
|
|
let _tpa = cc.v2();
|
|
let _tpb = cc.v2();
|
|
let _tpc = cc.v2();
|
|
|
|
let Particle = function () {
|
|
this.pos = cc.v2(0, 0);
|
|
this.startPos = cc.v2(0, 0);
|
|
this.color = cc.color(0, 0, 0, 255);
|
|
this.deltaColor = {r: 0, g: 0, b: 0, a: 255};
|
|
this.preciseColor = {r: 0, g: 0, b: 0, a: 255};
|
|
this.size = 0;
|
|
this.deltaSize = 0;
|
|
this.rotation = 0;
|
|
this.deltaRotation = 0;
|
|
this.timeToLive = 0;
|
|
this.drawPos = cc.v2(0, 0);
|
|
this.aspectRatio = 1;
|
|
// Mode A
|
|
this.dir = cc.v2(0, 0);
|
|
this.radialAccel = 0;
|
|
this.tangentialAccel = 0;
|
|
// Mode B
|
|
this.angle = 0;
|
|
this.degreesPerSecond = 0;
|
|
this.radius = 0;
|
|
this.deltaRadius = 0;
|
|
}
|
|
|
|
let pool = new js.Pool(function (par) {
|
|
par.pos.set(ZERO_VEC2);
|
|
par.startPos.set(ZERO_VEC2);
|
|
par.color._val = 0xFF000000;
|
|
par.deltaColor.r = par.deltaColor.g = par.deltaColor.b = 0;
|
|
par.deltaColor.a = 255;
|
|
par.size = 0;
|
|
par.deltaSize = 0;
|
|
par.rotation = 0;
|
|
par.deltaRotation = 0;
|
|
par.timeToLive = 0;
|
|
par.drawPos.set(ZERO_VEC2);
|
|
par.aspectRatio = 1;
|
|
// Mode A
|
|
par.dir.set(ZERO_VEC2);
|
|
par.radialAccel = 0;
|
|
par.tangentialAccel = 0;
|
|
// Mode B
|
|
par.angle = 0;
|
|
par.degreesPerSecond = 0;
|
|
par.radius = 0;
|
|
par.deltaRadius = 0;
|
|
}, 1024);
|
|
pool.get = function () {
|
|
return this._get() || new Particle();
|
|
}
|
|
|
|
let Simulator = function (system) {
|
|
this.sys = system;
|
|
this.particles = [];
|
|
this.active = false;
|
|
this.readyToPlay = true;
|
|
this.finished = false;
|
|
this.elapsed = 0;
|
|
this.emitCounter = 0;
|
|
this._uvFilled = 0;
|
|
this._worldRotation = 0;
|
|
}
|
|
|
|
Simulator.prototype.stop = function () {
|
|
this.active = false;
|
|
this.readyToPlay = false;
|
|
this.elapsed = this.sys.duration;
|
|
this.emitCounter = 0;
|
|
}
|
|
|
|
Simulator.prototype.reset = function () {
|
|
this.active = true;
|
|
this.readyToPlay = true;
|
|
this.elapsed = 0;
|
|
this.emitCounter = 0;
|
|
this.finished = false;
|
|
let particles = this.particles;
|
|
for (let id = 0; id < particles.length; ++id)
|
|
pool.put(particles[id]);
|
|
particles.length = 0;
|
|
let assembler = this.sys._assembler;
|
|
if (assembler && assembler._ia)
|
|
assembler._ia._count = 0;
|
|
}
|
|
|
|
Simulator.prototype.emitParticle = function (pos) {
|
|
let psys = this.sys;
|
|
let clampf = misc.clampf;
|
|
let particle = pool.get();
|
|
this.particles.push(particle);
|
|
|
|
// Init particle
|
|
// timeToLive
|
|
// no negative life. prevent division by 0
|
|
particle.timeToLive = psys.life + psys.lifeVar * (Math.random() - 0.5) * 2;
|
|
let timeToLive = particle.timeToLive = Math.max(0, particle.timeToLive);
|
|
|
|
// position
|
|
particle.pos.x = psys.sourcePos.x + psys.posVar.x * (Math.random() - 0.5) * 2;
|
|
particle.pos.y = psys.sourcePos.y + psys.posVar.y * (Math.random() - 0.5) * 2;
|
|
|
|
// Color
|
|
let sr, sg, sb, sa;
|
|
let startColor = psys._startColor, startColorVar = psys._startColorVar;
|
|
let endColor = psys._endColor, endColorVar = psys._endColorVar;
|
|
particle.color.r = sr = clampf(startColor.r + startColorVar.r * (Math.random() - 0.5) * 2, 0, 255);
|
|
particle.color.g = sg = clampf(startColor.g + startColorVar.g * (Math.random() - 0.5) * 2, 0, 255);
|
|
particle.color.b = sb = clampf(startColor.b + startColorVar.b * (Math.random() - 0.5) * 2, 0, 255);
|
|
particle.color.a = sa = clampf(startColor.a + startColorVar.a * (Math.random() - 0.5) * 2, 0, 255);
|
|
|
|
let color = particle.color;
|
|
let preciseColor = particle.preciseColor;
|
|
preciseColor.r = color.r;
|
|
preciseColor.g = color.g;
|
|
preciseColor.b = color.b;
|
|
preciseColor.a = color.a;
|
|
|
|
particle.deltaColor.r = (clampf(endColor.r + endColorVar.r * (Math.random() - 0.5) * 2, 0, 255) - sr) / timeToLive;
|
|
particle.deltaColor.g = (clampf(endColor.g + endColorVar.g * (Math.random() - 0.5) * 2, 0, 255) - sg) / timeToLive;
|
|
particle.deltaColor.b = (clampf(endColor.b + endColorVar.b * (Math.random() - 0.5) * 2, 0, 255) - sb) / timeToLive;
|
|
particle.deltaColor.a = (clampf(endColor.a + endColorVar.a * (Math.random() - 0.5) * 2, 0, 255) - sa) / timeToLive;
|
|
|
|
// size
|
|
let startS = psys.startSize + psys.startSizeVar * (Math.random() - 0.5) * 2;
|
|
startS = Math.max(0, startS); // No negative value
|
|
particle.size = startS;
|
|
if (psys.endSize === cc.ParticleSystem.START_SIZE_EQUAL_TO_END_SIZE) {
|
|
particle.deltaSize = 0;
|
|
} else {
|
|
var endS = psys.endSize + psys.endSizeVar * (Math.random() - 0.5) * 2;
|
|
endS = Math.max(0, endS); // No negative values
|
|
particle.deltaSize = (endS - startS) / timeToLive;
|
|
}
|
|
|
|
// rotation
|
|
var startA = psys.startSpin + psys.startSpinVar * (Math.random() - 0.5) * 2;
|
|
var endA = psys.endSpin + psys.endSpinVar * (Math.random() - 0.5) * 2;
|
|
particle.rotation = startA;
|
|
particle.deltaRotation = (endA - startA) / timeToLive;
|
|
|
|
// position
|
|
particle.startPos.x = pos.x;
|
|
particle.startPos.y = pos.y;
|
|
|
|
// aspect ratio
|
|
particle.aspectRatio = psys._aspectRatio || 1;
|
|
|
|
// direction
|
|
let a = misc.degreesToRadians( psys.angle + this._worldRotation + psys.angleVar * (Math.random() - 0.5) * 2);
|
|
// Mode Gravity: A
|
|
if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) {
|
|
let s = psys.speed + psys.speedVar * (Math.random() - 0.5) * 2;
|
|
// direction
|
|
particle.dir.x = Math.cos(a);
|
|
particle.dir.y = Math.sin(a);
|
|
particle.dir.mulSelf(s);
|
|
// radial accel
|
|
particle.radialAccel = psys.radialAccel + psys.radialAccelVar * (Math.random() - 0.5) * 2;
|
|
// tangential accel
|
|
particle.tangentialAccel = psys.tangentialAccel + psys.tangentialAccelVar * (Math.random() - 0.5) * 2;
|
|
// rotation is dir
|
|
if (psys.rotationIsDir) {
|
|
particle.rotation = -misc.radiansToDegrees(Math.atan2(particle.dir.y, particle.dir.x));
|
|
}
|
|
}
|
|
// Mode Radius: B
|
|
else {
|
|
// Set the default diameter of the particle from the source position
|
|
var startRadius = psys.startRadius + psys.startRadiusVar * (Math.random() - 0.5) * 2;
|
|
var endRadius = psys.endRadius + psys.endRadiusVar * (Math.random() - 0.5) * 2;
|
|
particle.radius = startRadius;
|
|
particle.deltaRadius = (psys.endRadius === cc.ParticleSystem.START_RADIUS_EQUAL_TO_END_RADIUS) ? 0 : (endRadius - startRadius) / timeToLive;
|
|
particle.angle = a;
|
|
particle.degreesPerSecond = misc.degreesToRadians(psys.rotatePerS + psys.rotatePerSVar * (Math.random() - 0.5) * 2);
|
|
}
|
|
};
|
|
|
|
// In the Free mode to get emit real rotation in the world coordinate.
|
|
function getWorldRotation (node) {
|
|
let rotation = 0;
|
|
let tempNode = node;
|
|
while (tempNode) {
|
|
rotation += tempNode.angle;
|
|
tempNode = tempNode.parent;
|
|
}
|
|
return rotation;
|
|
}
|
|
|
|
Simulator.prototype.updateUVs = function (force) {
|
|
let assembler = this.sys._assembler;
|
|
if (!assembler) {
|
|
return;
|
|
}
|
|
let buffer = assembler.getBuffer();
|
|
if (buffer && this.sys._renderSpriteFrame) {
|
|
const FLOAT_PER_PARTICLE = 4 * assembler._vfmt._bytes / 4;
|
|
let vbuf = buffer._vData;
|
|
let uv = this.sys._renderSpriteFrame.uv;
|
|
|
|
let start = force ? 0 : this._uvFilled;
|
|
let particleCount = this.particles.length;
|
|
for (let i = start; i < particleCount; i++) {
|
|
let offset = i * FLOAT_PER_PARTICLE;
|
|
vbuf[offset+2] = uv[0];
|
|
vbuf[offset+3] = uv[1];
|
|
vbuf[offset+7] = uv[2];
|
|
vbuf[offset+8] = uv[3];
|
|
vbuf[offset+12] = uv[4];
|
|
vbuf[offset+13] = uv[5];
|
|
vbuf[offset+17] = uv[6];
|
|
vbuf[offset+18] = uv[7];
|
|
}
|
|
this._uvFilled = particleCount;
|
|
}
|
|
}
|
|
|
|
Simulator.prototype.updateParticleBuffer = function (particle, pos, buffer, offset) {
|
|
let vbuf = buffer._vData;
|
|
let uintbuf = buffer._uintVData;
|
|
|
|
let x = pos.x, y = pos.y;
|
|
let width = particle.size;
|
|
let height = width;
|
|
let aspectRatio = particle.aspectRatio;
|
|
aspectRatio > 1 ? (height = width / aspectRatio) : (width = height * aspectRatio);
|
|
let halfWidth = width / 2;
|
|
let halfHeight = height / 2;
|
|
// pos
|
|
if (particle.rotation) {
|
|
let x1 = -halfWidth, y1 = -halfHeight;
|
|
let x2 = halfWidth, y2 = halfHeight;
|
|
let rad = -misc.degreesToRadians(particle.rotation);
|
|
let cr = Math.cos(rad), sr = Math.sin(rad);
|
|
// bl
|
|
vbuf[offset] = x1 * cr - y1 * sr + x;
|
|
vbuf[offset+1] = x1 * sr + y1 * cr + y;
|
|
// br
|
|
vbuf[offset+5] = x2 * cr - y1 * sr + x;
|
|
vbuf[offset+6] = x2 * sr + y1 * cr + y;
|
|
// tl
|
|
vbuf[offset+10] = x1 * cr - y2 * sr + x;
|
|
vbuf[offset+11] = x1 * sr + y2 * cr + y;
|
|
// tr
|
|
vbuf[offset+15] = x2 * cr - y2 * sr + x;
|
|
vbuf[offset+16] = x2 * sr + y2 * cr + y;
|
|
}
|
|
else {
|
|
// bl
|
|
vbuf[offset] = x - halfWidth;
|
|
vbuf[offset+1] = y - halfHeight;
|
|
// br
|
|
vbuf[offset+5] = x + halfWidth;
|
|
vbuf[offset+6] = y - halfHeight;
|
|
// tl
|
|
vbuf[offset+10] = x - halfWidth;
|
|
vbuf[offset+11] = y + halfHeight;
|
|
// tr
|
|
vbuf[offset+15] = x + halfWidth;
|
|
vbuf[offset+16] = y + halfHeight;
|
|
}
|
|
// color
|
|
uintbuf[offset+4] = particle.color._val;
|
|
uintbuf[offset+9] = particle.color._val;
|
|
uintbuf[offset+14] = particle.color._val;
|
|
uintbuf[offset+19] = particle.color._val;
|
|
};
|
|
|
|
Simulator.prototype.step = function (dt) {
|
|
dt = dt > cc.director._maxParticleDeltaTime ? cc.director._maxParticleDeltaTime : dt;
|
|
let psys = this.sys;
|
|
let node = psys.node;
|
|
let particles = this.particles;
|
|
const FLOAT_PER_PARTICLE = 4 * this.sys._assembler._vfmt._bytes / 4;
|
|
const PositionType = cc.ParticleSystem.PositionType;
|
|
|
|
// Calculate pos
|
|
node._updateWorldMatrix();
|
|
if (psys.positionType === PositionType.FREE) {
|
|
this._worldRotation = getWorldRotation(node);
|
|
let m = node._worldMatrix.m;
|
|
_pos.x = m[12];
|
|
_pos.y = m[13];
|
|
} else if (psys.positionType === PositionType.RELATIVE) {
|
|
this._worldRotation = node.angle;
|
|
_pos.x = node.x;
|
|
_pos.y = node.y;
|
|
} else {
|
|
this._worldRotation = 0;
|
|
}
|
|
|
|
// Emission
|
|
if (this.active && psys.emissionRate) {
|
|
var rate = 1.0 / psys.emissionRate;
|
|
//issue #1201, prevent bursts of particles, due to too high emitCounter
|
|
if (particles.length < psys.totalParticles)
|
|
this.emitCounter += dt;
|
|
|
|
while ((particles.length < psys.totalParticles) && (this.emitCounter > rate)) {
|
|
this.emitParticle(_pos);
|
|
this.emitCounter -= rate;
|
|
}
|
|
|
|
this.elapsed += dt;
|
|
if (psys.duration !== -1 && psys.duration < this.elapsed) {
|
|
psys.stopSystem();
|
|
}
|
|
}
|
|
|
|
// Request buffer for particles
|
|
let buffer = psys._assembler.getBuffer();
|
|
let particleCount = particles.length;
|
|
buffer.reset();
|
|
buffer.request(particleCount * 4, particleCount * 6);
|
|
|
|
// Fill up uvs
|
|
if (particleCount > this._uvFilled) {
|
|
this.updateUVs();
|
|
}
|
|
|
|
// Used to reduce memory allocation / creation within the loop
|
|
let particleIdx = 0;
|
|
while (particleIdx < particles.length) {
|
|
// Reset temporary vectors
|
|
_tpa.x = _tpa.y = _tpb.x = _tpb.y = _tpc.x = _tpc.y = 0;
|
|
|
|
let particle = particles[particleIdx];
|
|
|
|
// life
|
|
particle.timeToLive -= dt;
|
|
if (particle.timeToLive > 0) {
|
|
// Mode A: gravity, direction, tangential accel & radial accel
|
|
if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) {
|
|
let tmp = _tpc, radial = _tpa, tangential = _tpb;
|
|
|
|
// radial acceleration
|
|
if (particle.pos.x || particle.pos.y) {
|
|
radial.set(particle.pos);
|
|
radial.normalizeSelf();
|
|
}
|
|
tangential.set(radial);
|
|
radial.mulSelf(particle.radialAccel);
|
|
|
|
// tangential acceleration
|
|
let newy = tangential.x;
|
|
tangential.x = -tangential.y;
|
|
tangential.y = newy;
|
|
|
|
tangential.mulSelf(particle.tangentialAccel);
|
|
|
|
tmp.set(radial);
|
|
tmp.addSelf(tangential);
|
|
tmp.addSelf(psys.gravity);
|
|
tmp.mulSelf(dt);
|
|
particle.dir.addSelf(tmp);
|
|
|
|
tmp.set(particle.dir);
|
|
tmp.mulSelf(dt);
|
|
particle.pos.addSelf(tmp);
|
|
}
|
|
// Mode B: radius movement
|
|
else {
|
|
// Update the angle and radius of the particle.
|
|
particle.angle += particle.degreesPerSecond * dt;
|
|
particle.radius += particle.deltaRadius * dt;
|
|
|
|
particle.pos.x = -Math.cos(particle.angle) * particle.radius;
|
|
particle.pos.y = -Math.sin(particle.angle) * particle.radius;
|
|
}
|
|
|
|
// color
|
|
let preciseColor = particle.preciseColor;
|
|
let deltaColor = particle.deltaColor;
|
|
preciseColor.r += deltaColor.r * dt;
|
|
preciseColor.g += deltaColor.g * dt;
|
|
preciseColor.b += deltaColor.b * dt;
|
|
preciseColor.a += deltaColor.a * dt;
|
|
|
|
let color = particle.color;
|
|
color.r = preciseColor.r;
|
|
color.g = preciseColor.g;
|
|
color.b = preciseColor.b;
|
|
color.a = preciseColor.a;
|
|
|
|
// size
|
|
particle.size += particle.deltaSize * dt;
|
|
if (particle.size < 0) {
|
|
particle.size = 0;
|
|
}
|
|
|
|
// angle
|
|
particle.rotation += particle.deltaRotation * dt;
|
|
|
|
// update values in quad buffer
|
|
let newPos = _tpa;
|
|
newPos.set(particle.pos);
|
|
if (psys.positionType !== PositionType.GROUPED) {
|
|
newPos.addSelf(particle.startPos);
|
|
}
|
|
|
|
let offset = FLOAT_PER_PARTICLE * particleIdx;
|
|
this.updateParticleBuffer(particle, newPos, buffer, offset);
|
|
|
|
// update particle counter
|
|
++particleIdx;
|
|
} else {
|
|
// life < 0
|
|
let deadParticle = particles[particleIdx];
|
|
if (particleIdx !== particles.length - 1) {
|
|
particles[particleIdx] = particles[particles.length - 1];
|
|
}
|
|
pool.put(deadParticle);
|
|
particles.length--;
|
|
}
|
|
}
|
|
|
|
psys._assembler._ia._count = particles.length * 6;
|
|
if (particles.length > 0) {
|
|
buffer.uploadData();
|
|
}
|
|
else if (!this.active && !this.readyToPlay) {
|
|
this.finished = true;
|
|
psys._finishedSimulation();
|
|
}
|
|
}
|
|
|
|
module.exports = Simulator;
|