diff --git a/core/2d/CCParticleSystem.cpp b/core/2d/CCParticleSystem.cpp index daaf7e9928..ff7c74a3ac 100644 --- a/core/2d/CCParticleSystem.cpp +++ b/core/2d/CCParticleSystem.cpp @@ -97,21 +97,6 @@ inline void normalize_point(float x, float y, particle_point* out) out->y = y * n; } -/** - A more effect random number getter function, get from ejoy2d. - */ -inline static float RANDOM_M11(unsigned int* seed) -{ - *seed = *seed * 134775813 + 1; - union - { - uint32_t d; - float f; - } u; - u.d = (((uint32_t)(*seed) & 0x7fff) << 8) | 0x40000000; - return u.f - 3.0f; -} - ParticleData::ParticleData() { memset(this, 0, sizeof(ParticleData)); @@ -121,24 +106,27 @@ bool ParticleData::init(int count) { maxCount = count; - posx = (float*)malloc(count * sizeof(float)); - posy = (float*)malloc(count * sizeof(float)); - startPosX = (float*)malloc(count * sizeof(float)); - startPosY = (float*)malloc(count * sizeof(float)); - colorR = (float*)malloc(count * sizeof(float)); - colorG = (float*)malloc(count * sizeof(float)); - colorB = (float*)malloc(count * sizeof(float)); - colorA = (float*)malloc(count * sizeof(float)); - deltaColorR = (float*)malloc(count * sizeof(float)); - deltaColorG = (float*)malloc(count * sizeof(float)); - deltaColorB = (float*)malloc(count * sizeof(float)); - deltaColorA = (float*)malloc(count * sizeof(float)); - size = (float*)malloc(count * sizeof(float)); - deltaSize = (float*)malloc(count * sizeof(float)); - rotation = (float*)malloc(count * sizeof(float)); - deltaRotation = (float*)malloc(count * sizeof(float)); - timeToLive = (float*)malloc(count * sizeof(float)); - atlasIndex = (unsigned int*)malloc(count * sizeof(unsigned int)); + posx = (float*)malloc(count * sizeof(float)); + posy = (float*)malloc(count * sizeof(float)); + startPosX = (float*)malloc(count * sizeof(float)); + startPosY = (float*)malloc(count * sizeof(float)); + colorR = (float*)malloc(count * sizeof(float)); + colorG = (float*)malloc(count * sizeof(float)); + colorB = (float*)malloc(count * sizeof(float)); + colorA = (float*)malloc(count * sizeof(float)); + deltaColorR = (float*)malloc(count * sizeof(float)); + deltaColorG = (float*)malloc(count * sizeof(float)); + deltaColorB = (float*)malloc(count * sizeof(float)); + deltaColorA = (float*)malloc(count * sizeof(float)); + + size = (float*)malloc(count * sizeof(float)); + deltaSize = (float*)malloc(count * sizeof(float)); + rotation = (float*)malloc(count * sizeof(float)); + staticRotation = (float*)malloc(count * sizeof(float)); + deltaRotation = (float*)malloc(count * sizeof(float)); + totalTimeToLive = (float*)malloc(count * sizeof(float)); + timeToLive = (float*)malloc(count * sizeof(float)); + atlasIndex = (unsigned int*)malloc(count * sizeof(unsigned int)); modeA.dirX = (float*)malloc(count * sizeof(float)); modeA.dirY = (float*)malloc(count * sizeof(float)); @@ -150,10 +138,11 @@ bool ParticleData::init(int count) modeB.deltaRadius = (float*)malloc(count * sizeof(float)); modeB.radius = (float*)malloc(count * sizeof(float)); - return posx && posy && startPosY && startPosX && colorR && colorG && colorB && colorA && deltaColorR && - deltaColorG && deltaColorB && deltaColorA && size && deltaSize && rotation && deltaRotation && timeToLive && - atlasIndex && modeA.dirX && modeA.dirY && modeA.radialAccel && modeA.tangentialAccel && modeB.angle && - modeB.degreesPerSecond && modeB.deltaRadius && modeB.radius; + return posx && posy && startPosX && startPosY && colorR && colorG && colorB && colorA && deltaColorR && + deltaColorG && deltaColorB && deltaColorA && size && deltaSize && rotation && staticRotation && + deltaRotation && totalTimeToLive && timeToLive && atlasIndex && modeA.dirX && modeA.dirY && + modeA.radialAccel && modeA.tangentialAccel && modeB.angle && modeB.degreesPerSecond && modeB.deltaRadius && + modeB.radius; } void ParticleData::release() @@ -170,11 +159,24 @@ void ParticleData::release() CC_SAFE_FREE(deltaColorG); CC_SAFE_FREE(deltaColorB); CC_SAFE_FREE(deltaColorA); + CC_SAFE_FREE(hue); + CC_SAFE_FREE(sat); + CC_SAFE_FREE(val); + CC_SAFE_FREE(opacityFadeInDelta); + CC_SAFE_FREE(opacityFadeInLength); + CC_SAFE_FREE(scaleInDelta); + CC_SAFE_FREE(scaleInLength); CC_SAFE_FREE(size); CC_SAFE_FREE(deltaSize); CC_SAFE_FREE(rotation); + CC_SAFE_FREE(staticRotation); CC_SAFE_FREE(deltaRotation); + CC_SAFE_FREE(totalTimeToLive); CC_SAFE_FREE(timeToLive); + CC_SAFE_FREE(animTimeLength); + CC_SAFE_FREE(animTimeDelta); + CC_SAFE_FREE(animIndex); + CC_SAFE_FREE(animCellIndex); CC_SAFE_FREE(atlasIndex); CC_SAFE_FREE(modeA.dirX); @@ -202,6 +204,10 @@ ParticleSystem::ParticleSystem() , _atlasIndex(0) , _transformSystemDirty(false) , _allocatedParticles(0) + , _isAnimAllocated(false) + , _isHSVAllocated(false) + , _isOpacityFadeInAllocated(false) + , _isScaleInAllocated(false) , _isActive(true) , _particleCount(0) , _duration(0) @@ -218,14 +224,34 @@ ParticleSystem::ParticleSystem() , _startSpinVar(0) , _endSpin(0) , _endSpinVar(0) + , _spawnAngle(0) + , _spawnAngleVar(0) + , _hsv(0, 1, 1) + , _hsvVar(0, 0, 0) + , _spawnFadeIn(0) + , _spawnFadeInVar(0) + , _spawnScaleIn(0) + , _spawnScaleInVar(0) , _emissionRate(0) , _totalParticles(0) , _texture(nullptr) , _blendFunc(BlendFunc::ALPHA_PREMULTIPLIED) , _opacityModifyRGB(false) + , _isLifeAnimated(false) + , _isEmitterAnimated(false) + , _isLoopAnimated(false) + , _animIndexCount(0) + , _isAnimationReversed(false) + , _undefinedIndexRect({0, 0, 0, 0}) + , _animationTimescaleInd(false) , _yCoordFlipped(1) + , _isEmissionShapes(false) + , _emissionShapeIndex(0) , _positionType(PositionType::FREE) , _paused(false) + , _timeScale(1) + , _fixedFPS(0) + , _fixedFPSDelta(0) , _sourcePositionCompatible(true) // In the furture this member's default value maybe false or be removed. { modeA.gravity.setZero(); @@ -275,6 +301,101 @@ Vector& ParticleSystem::getAllParticleSystems() return __allInstances; } +bool ParticleSystem::allocAnimationMem() +{ + if (!_isAnimAllocated) + { + _particleData.animTimeLength = (float*)malloc(_totalParticles * sizeof(float)); + _particleData.animTimeDelta = (float*)malloc(_totalParticles * sizeof(float)); + _particleData.animIndex = (unsigned short*)malloc(_totalParticles * sizeof(unsigned short)); + _particleData.animCellIndex = (unsigned short*)malloc(_totalParticles * sizeof(unsigned short)); + if (_particleData.animTimeLength && _particleData.animTimeDelta && _particleData.animIndex && + _particleData.animCellIndex) + return _isAnimAllocated = true; + else + // If any of the above allocations fail, then we safely deallocate the ones that succeeded. + deallocAnimationMem(); + } + return false; +} + +void ParticleSystem::deallocAnimationMem() +{ + CC_SAFE_FREE(_particleData.animTimeLength); + CC_SAFE_FREE(_particleData.animTimeDelta); + CC_SAFE_FREE(_particleData.animIndex); + CC_SAFE_FREE(_particleData.animCellIndex); + _isAnimAllocated = false; +} + +bool ParticleSystem::allocHSVMem() +{ + if (!_isHSVAllocated) + { + _particleData.hue = (float*)malloc(_totalParticles * sizeof(float)); + _particleData.sat = (float*)malloc(_totalParticles * sizeof(float)); + _particleData.val = (float*)malloc(_totalParticles * sizeof(float)); + if (_particleData.hue && _particleData.sat && _particleData.val) + return _isHSVAllocated = true; + else + // If any of the above allocations fail, then we safely deallocate the ones that succeeded. + deallocHSVMem(); + } + return false; +} + +void ParticleSystem::deallocHSVMem() +{ + CC_SAFE_FREE(_particleData.hue); + CC_SAFE_FREE(_particleData.sat); + CC_SAFE_FREE(_particleData.val); + _isHSVAllocated = false; +} + +bool ParticleSystem::allocOpacityFadeInMem() +{ + if (!_isOpacityFadeInAllocated) + { + _particleData.opacityFadeInDelta = (float*)malloc(_totalParticles * sizeof(float)); + _particleData.opacityFadeInLength = (float*)malloc(_totalParticles * sizeof(float)); + if (_particleData.opacityFadeInDelta && _particleData.opacityFadeInLength) + return _isOpacityFadeInAllocated = true; + else + // If any of the above allocations fail, then we safely deallocate the ones that succeeded. + deallocOpacityFadeInMem(); + } + return false; +} + +void ParticleSystem::deallocOpacityFadeInMem() +{ + CC_SAFE_FREE(_particleData.opacityFadeInDelta); + CC_SAFE_FREE(_particleData.opacityFadeInLength); + _isOpacityFadeInAllocated = false; +} + +bool ParticleSystem::allocScaleInMem() +{ + if (!_isScaleInAllocated) + { + _particleData.scaleInDelta = (float*)malloc(_totalParticles * sizeof(float)); + _particleData.scaleInLength = (float*)malloc(_totalParticles * sizeof(float)); + if (_particleData.scaleInDelta && _particleData.scaleInLength) + return _isScaleInAllocated = true; + else + // If any of the above allocations fail, then we safely deallocate the ones that succeeded. + deallocScaleInMem(); + } + return false; +} + +void ParticleSystem::deallocScaleInMem() +{ + CC_SAFE_FREE(_particleData.scaleInDelta); + CC_SAFE_FREE(_particleData.scaleInLength); + _isScaleInAllocated = false; +} + void ParticleSystem::setTotalParticleCountFactor(float factor) { __totalParticleCountFactor = factor; @@ -604,14 +725,20 @@ ParticleSystem::~ParticleSystem() // it is not needed to call "unscheduleUpdate" here. In fact, it will be called in "cleanup" // unscheduleUpdate(); _particleData.release(); + _animations.clear(); CC_SAFE_RELEASE(_texture); } -void ParticleSystem::addParticles(int count) +void ParticleSystem::addParticles(int count, int animationIndex, int animationCellIndex) { if (_paused) return; - uint32_t RANDSEED = rand(); + + // Try to add as many particles as possible without overflowing. + count = MIN(int(_totalParticles * __totalParticleCountFactor) - _particleCount, count); + + animationCellIndex = MIN(animationCellIndex, _animIndexCount - 1); + animationIndex = MIN(animationIndex, _animIndexCount - 1); int start = _particleCount; _particleCount += count; @@ -619,26 +746,181 @@ void ParticleSystem::addParticles(int count) // life for (int i = start; i < _particleCount; ++i) { - float theLife = _life + _lifeVar * RANDOM_M11(&RANDSEED); - _particleData.timeToLive[i] = MAX(0, theLife); + float particleLife = _life + _lifeVar * _rng.rangef(); + _particleData.totalTimeToLive[i] = MAX(0, particleLife); + _particleData.timeToLive[i] = MAX(0, particleLife); } - // position - for (int i = start; i < _particleCount; ++i) + if (_isEmissionShapes) { - _particleData.posx[i] = _sourcePosition.x + _posVar.x * RANDOM_M11(&RANDSEED); + for (int i = start; i < _particleCount; ++i) + { + if (_emissionShapes.empty()) + { + _particleData.posx[i] = _sourcePosition.x + _posVar.x * _rng.rangef(); + _particleData.posy[i] = _sourcePosition.y + _posVar.y * _rng.rangef(); + continue; + } + + auto randElem = _rng.float01(); + auto& shape = _emissionShapes[MIN(randElem * _emissionShapes.size(), _emissionShapes.size() - 1)]; + + switch (shape.type) + { + case EmissionShapeType::POINT: + { + _particleData.posx[i] = _sourcePosition.x + shape.x; + _particleData.posy[i] = _sourcePosition.y + shape.y; + + break; + } + case EmissionShapeType::RECT: + { + _particleData.posx[i] = _sourcePosition.x + shape.x + shape.innerWidth / 2 * _rng.rangef(); + _particleData.posy[i] = _sourcePosition.y + shape.y + shape.innerHeight / 2 * _rng.rangef(); + + break; + } + case EmissionShapeType::RECTTORUS: + { + float width = (shape.outerWidth - shape.innerWidth) * _rng.float01() + shape.innerWidth; + float height = (shape.outerHeight - shape.innerHeight) * _rng.float01() + shape.innerHeight; + width = _rng.rangef() < 0.0F ? width * -1 : width; + height = _rng.rangef() < 0.0F ? height * -1 : height; + float prob = _rng.rangef(); + _particleData.posx[i] = _sourcePosition.x + shape.x + width / 2 * (prob >= 0.0F ? 1.0F : _rng.rangef()); + _particleData.posy[i] = _sourcePosition.y + shape.y + height / 2 * (prob < 0.0F ? 1.0F : _rng.rangef()); + + break; + } + case EmissionShapeType::CIRCLE: + { + auto val = _rng.float01() * shape.innerRadius / shape.innerRadius; + val = powf(val, 1 / shape.edgeBias); + auto point = Vec2(0.0F, val * shape.innerRadius); + point = point.rotateByAngle(Vec2::ZERO, -CC_DEGREES_TO_RADIANS(shape.coneOffset + shape.coneAngle / 2 * _rng.rangef())); + _particleData.posx[i] = _sourcePosition.x + shape.x + point.x / 2; + _particleData.posy[i] = _sourcePosition.y + shape.y + point.y / 2; + + break; + } + case EmissionShapeType::TORUS: + { + auto val = _rng.float01() * shape.outerRadius / shape.outerRadius; + val = powf(val, 1 / shape.edgeBias); + auto point = Vec2(0.0F, ((val * (shape.outerRadius - shape.innerRadius) + shape.outerRadius) - (shape.outerRadius - shape.innerRadius))); + point = point.rotateByAngle(Vec2::ZERO, -CC_DEGREES_TO_RADIANS(shape.coneOffset + shape.coneAngle / 2 * _rng.rangef())); + _particleData.posx[i] = _sourcePosition.x + shape.x + point.x / 2; + _particleData.posy[i] = _sourcePosition.y + shape.y + point.y / 2; + + break; + } + case EmissionShapeType::ALPHA_MASK: + { + auto& mask = ParticleEmissionMaskCache::getInstance()->getEmissionMask(shape.fourccId); + + Vec2 pos = {shape.x, shape.y}; + Vec2 size = mask.size; + Vec2 overrideSize = {shape.innerWidth, shape.innerHeight}; + Vec2 scale = {shape.outerWidth, shape.outerHeight}; + float angle = shape.coneOffset; + + if (overrideSize.isZero()) + overrideSize = mask.size; + + Vec2 point = {0, 0}; + + int rand0 = _rng.float01() * mask.points.size(); + int index = MIN(rand0, mask.points.size() - 1); + point = mask.points[index]; + + point -= size / 2; + + point.x = point.x / size.x * overrideSize.x * scale.x; + point.y = point.y / size.y * overrideSize.y * scale.y; + + point = point.rotateByAngle(Vec2::ZERO, -CC_DEGREES_TO_RADIANS(angle)); + + _particleData.posx[i] = _sourcePosition.x + shape.x + point.x; + _particleData.posy[i] = _sourcePosition.y + shape.y + point.y; + + break; + } + } + } + } + else + { + // position + for (int i = start; i < _particleCount; ++i) + { + _particleData.posx[i] = _sourcePosition.x + _posVar.x * _rng.rangef(); + } + + for (int i = start; i < _particleCount; ++i) + { + _particleData.posy[i] = _sourcePosition.y + _posVar.y * _rng.rangef(); + } } - for (int i = start; i < _particleCount; ++i) + if (animationCellIndex != -1 || animationIndex != -1) + allocAnimationMem(); + + if (_isAnimAllocated) { - _particleData.posy[i] = _sourcePosition.y + _posVar.y * RANDOM_M11(&RANDSEED); + if (animationCellIndex != -1) + std::fill_n(_particleData.animCellIndex + start, _particleCount - start, animationCellIndex); + else + std::fill_n(_particleData.animCellIndex + start, _particleCount - start, 0xFFFF); + + if (animationIndex != -1) + { + for (int i = start; i < _particleCount; ++i) + { + _particleData.animIndex[i] = animationIndex; + auto& descriptor = _animations.at(animationIndex); + _particleData.animTimeLength[i] = + descriptor.animationSpeed + descriptor.animationSpeedVariance * _rng.rangef(); + } + } + } + + if (_isLifeAnimated || _isEmitterAnimated || _isLoopAnimated) + { + if (animationCellIndex == -1 && _isEmitterAnimated) + { + for (int i = start; i < _particleCount; ++i) + { + int rand0 = _rng.float01() * _animIndexCount; + _particleData.animCellIndex[i] = MIN(rand0, _animIndexCount - 1); + } + } + + if (animationIndex == -1 && !_animations.empty()) + { + if (_randomAnimations.empty()) + setMultiAnimationRandom(); + + for (int i = start; i < _particleCount; ++i) + { + int rand0 = _rng.float01() * _randomAnimations.size(); + int index = MIN(rand0, _randomAnimations.size() - 1); + _particleData.animIndex[i] = _randomAnimations[index]; + auto& descriptor = _animations.at(_particleData.animIndex[i]); + _particleData.animTimeLength[i] = + descriptor.animationSpeed + descriptor.animationSpeedVariance * _rng.rangef(); + } + } + + if (_isEmitterAnimated || _isLoopAnimated) + std::fill_n(_particleData.animTimeDelta + start, _particleCount - start, 0); } // color -#define SET_COLOR(c, b, v) \ - for (int i = start; i < _particleCount; ++i) \ - { \ - c[i] = clampf(b + v * RANDOM_M11(&RANDSEED), 0, 1); \ +#define SET_COLOR(c, b, v) \ + for (int i = start; i < _particleCount; ++i) \ + { \ + c[i] = clampf(b + v * _rng.rangef(), 0, 1); \ } SET_COLOR(_particleData.colorR, _startColor.r, _startColorVar.r); @@ -662,10 +944,49 @@ void ParticleSystem::addParticles(int count) SET_DELTA_COLOR(_particleData.colorB, _particleData.deltaColorB); SET_DELTA_COLOR(_particleData.colorA, _particleData.deltaColorA); + // opacity fade in + if (_isOpacityFadeInAllocated) + { + for (int i = start; i < _particleCount; ++i) + { + _particleData.opacityFadeInLength[i] = _spawnFadeIn + _spawnFadeInVar * _rng.rangef(); + } + std::fill_n(_particleData.opacityFadeInDelta + start, _particleCount - start, 0.0F); + } + + // scale fade in + if (_isScaleInAllocated) + { + for (int i = start; i < _particleCount; ++i) + { + _particleData.scaleInLength[i] = _spawnScaleIn + _spawnScaleInVar * _rng.rangef(); + } + std::fill_n(_particleData.scaleInDelta + start, _particleCount - start, 0.0F); + } + + // hue saturation value color + if (_isHSVAllocated) + { + for (int i = start; i < _particleCount; ++i) + { + _particleData.hue[i] = _hsv.h + _hsvVar.h * _rng.rangef(); + } + + for (int i = start; i < _particleCount; ++i) + { + _particleData.sat[i] = _hsv.s + _hsvVar.s * _rng.rangef(); + } + + for (int i = start; i < _particleCount; ++i) + { + _particleData.val[i] = _hsv.v + _hsvVar.v * _rng.rangef(); + } + } + // size for (int i = start; i < _particleCount; ++i) { - _particleData.size[i] = _startSize + _startSizeVar * RANDOM_M11(&RANDSEED); + _particleData.size[i] = _startSize + _startSizeVar * _rng.rangef(); _particleData.size[i] = MAX(0, _particleData.size[i]); } @@ -673,30 +994,31 @@ void ParticleSystem::addParticles(int count) { for (int i = start; i < _particleCount; ++i) { - float endSize = _endSize + _endSizeVar * RANDOM_M11(&RANDSEED); + float endSize = _endSize + _endSizeVar * _rng.rangef(); endSize = MAX(0, endSize); _particleData.deltaSize[i] = (endSize - _particleData.size[i]) / _particleData.timeToLive[i]; } } else - { - for (int i = start; i < _particleCount; ++i) - { - _particleData.deltaSize[i] = 0.0f; - } - } + std::fill_n(_particleData.deltaSize + start, _particleCount - start, 0.0F); // rotation for (int i = start; i < _particleCount; ++i) { - _particleData.rotation[i] = _startSpin + _startSpinVar * RANDOM_M11(&RANDSEED); + _particleData.rotation[i] = _startSpin + _startSpinVar * _rng.rangef(); } for (int i = start; i < _particleCount; ++i) { - float endA = _endSpin + _endSpinVar * RANDOM_M11(&RANDSEED); + float endA = _endSpin + _endSpinVar * _rng.rangef(); _particleData.deltaRotation[i] = (endA - _particleData.rotation[i]) / _particleData.timeToLive[i]; } + // static rotation + for (int i = start; i < _particleCount; ++i) + { + _particleData.staticRotation[i] = _spawnAngle + _spawnAngleVar * _rng.rangef(); + } + // position Vec2 pos; if (_positionType == PositionType::FREE) @@ -707,14 +1029,8 @@ void ParticleSystem::addParticles(int count) { pos = _position; } - for (int i = start; i < _particleCount; ++i) - { - _particleData.startPosX[i] = pos.x; - } - for (int i = start; i < _particleCount; ++i) - { - _particleData.startPosY[i] = pos.y; - } + std::fill_n(_particleData.startPosX + start, _particleCount - start, pos.x); + std::fill_n(_particleData.startPosY + start, _particleCount - start, pos.y); // Mode Gravity: A if (_emitterMode == Mode::GRAVITY) @@ -723,14 +1039,13 @@ void ParticleSystem::addParticles(int count) // radial accel for (int i = start; i < _particleCount; ++i) { - _particleData.modeA.radialAccel[i] = modeA.radialAccel + modeA.radialAccelVar * RANDOM_M11(&RANDSEED); + _particleData.modeA.radialAccel[i] = modeA.radialAccel + modeA.radialAccelVar * _rng.rangef(); } // tangential accel for (int i = start; i < _particleCount; ++i) { - _particleData.modeA.tangentialAccel[i] = - modeA.tangentialAccel + modeA.tangentialAccelVar * RANDOM_M11(&RANDSEED); + _particleData.modeA.tangentialAccel[i] = modeA.tangentialAccel + modeA.tangentialAccelVar * _rng.rangef(); } // rotation is dir @@ -738,9 +1053,9 @@ void ParticleSystem::addParticles(int count) { for (int i = start; i < _particleCount; ++i) { - float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * RANDOM_M11(&RANDSEED)); + float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * _rng.rangef()); Vec2 v(cosf(a), sinf(a)); - float s = modeA.speed + modeA.speedVar * RANDOM_M11(&RANDSEED); + float s = modeA.speed + modeA.speedVar * _rng.rangef(); Vec2 dir = v * s; _particleData.modeA.dirX[i] = dir.x; // v * s ; _particleData.modeA.dirY[i] = dir.y; @@ -751,9 +1066,9 @@ void ParticleSystem::addParticles(int count) { for (int i = start; i < _particleCount; ++i) { - float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * RANDOM_M11(&RANDSEED)); + float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * _rng.rangef()); Vec2 v(cosf(a), sinf(a)); - float s = modeA.speed + modeA.speedVar * RANDOM_M11(&RANDSEED); + float s = modeA.speed + modeA.speedVar * _rng.rangef(); Vec2 dir = v * s; _particleData.modeA.dirX[i] = dir.x; // v * s ; _particleData.modeA.dirY[i] = dir.y; @@ -768,32 +1083,27 @@ void ParticleSystem::addParticles(int count) // Set the default diameter of the particle from the source position for (int i = start; i < _particleCount; ++i) { - _particleData.modeB.radius[i] = modeB.startRadius + modeB.startRadiusVar * RANDOM_M11(&RANDSEED); + _particleData.modeB.radius[i] = modeB.startRadius + modeB.startRadiusVar * _rng.rangef(); } for (int i = start; i < _particleCount; ++i) { - _particleData.modeB.angle[i] = CC_DEGREES_TO_RADIANS(_angle + _angleVar * RANDOM_M11(&RANDSEED)); + _particleData.modeB.angle[i] = CC_DEGREES_TO_RADIANS(_angle + _angleVar * _rng.rangef()); } for (int i = start; i < _particleCount; ++i) { _particleData.modeB.degreesPerSecond[i] = - CC_DEGREES_TO_RADIANS(modeB.rotatePerSecond + modeB.rotatePerSecondVar * RANDOM_M11(&RANDSEED)); + CC_DEGREES_TO_RADIANS(modeB.rotatePerSecond + modeB.rotatePerSecondVar * _rng.rangef()); } if (modeB.endRadius == START_RADIUS_EQUAL_TO_END_RADIUS) - { - for (int i = start; i < _particleCount; ++i) - { - _particleData.modeB.deltaRadius[i] = 0.0f; - } - } + std::fill_n(_particleData.modeB.deltaRadius + start, _particleCount - start, 0.0F); else { for (int i = start; i < _particleCount; ++i) { - float endRadius = modeB.endRadius + modeB.endRadiusVar * RANDOM_M11(&RANDSEED); + float endRadius = modeB.endRadius + modeB.endRadiusVar * _rng.rangef(); _particleData.modeB.deltaRadius[i] = (endRadius - _particleData.modeB.radius[i]) / _particleData.timeToLive[i]; } @@ -801,6 +1111,398 @@ void ParticleSystem::addParticles(int count) } } +void ParticleSystem::setAnimationDescriptor(unsigned short indexOfDescriptor, + float time, + float timeVariance, + const std::vector& indices, + bool reverse) +{ + auto iter = _animations.find(indexOfDescriptor); + if (iter == _animations.end()) + iter = _animations.emplace(indexOfDescriptor, ParticleAnimationDescriptor{}).first; + + auto& desc = iter->second; + desc.animationSpeed = time; + desc.animationSpeedVariance = timeVariance; + desc.animationIndices = std::move(indices); + desc.reverseIndices = reverse; +} + +void ParticleSystem::resetEmissionShapes() +{ + _emissionShapeIndex = 0; + _emissionShapes.clear(); +} + +void ParticleSystem::addEmissionShape(EmissionShape shape) +{ + setEmissionShape(_emissionShapeIndex, shape); +} + +void ParticleSystem::setEmissionShape(unsigned short index, EmissionShape shape) +{ + auto iter = _emissionShapes.find(index); + if (iter == _emissionShapes.end()) + { + iter = _emissionShapes.emplace(index, EmissionShape{}).first; + _emissionShapeIndex++; + } + + iter->second = shape; +} + +EmissionShape ParticleSystem::createMaskShape(std::string_view maskId, + Vec2 pos, + Vec2 overrideSize, + Vec2 scale, + float angle) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::ALPHA_MASK; + + shape.fourccId = utils::fourccValue(maskId); + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerWidth = overrideSize.x; + shape.innerHeight = overrideSize.y; + + shape.outerWidth = scale.x; + shape.outerHeight = scale.y; + + shape.coneOffset = angle; + + return shape; +} + +EmissionShape ParticleSystem::createPointShape(Vec2 pos) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::POINT; + + shape.x = pos.x; + shape.y = pos.y; + + return shape; +} + +EmissionShape ParticleSystem::createRectShape(Vec2 pos, Size size) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::RECT; + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerWidth = size.x; + shape.innerHeight = size.y; + + return shape; +} + +EmissionShape ParticleSystem::createRectTorusShape(Vec2 pos, Size innerSize, Size outerSize) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::RECTTORUS; + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerWidth = innerSize.x; + shape.innerHeight = innerSize.y; + + shape.outerWidth = outerSize.x; + shape.outerHeight = outerSize.y; + + return shape; +} + +EmissionShape ParticleSystem::createCircleShape(Vec2 pos, float radius, float edgeBias) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::CIRCLE; + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerRadius = radius; + + shape.coneOffset = 0; + shape.coneAngle = 360; + + shape.edgeBias = edgeBias; + + return shape; +} + +EmissionShape ParticleSystem::createConeShape(Vec2 pos, float radius, float offset, float angle, float edgeBias) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::CIRCLE; + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerRadius = radius; + + shape.coneOffset = offset; + shape.coneAngle = angle; + + shape.edgeBias = edgeBias; + + return shape; +} + +EmissionShape ParticleSystem::createTorusShape(Vec2 pos, float innerRadius, float outerRadius, float edgeBias) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::TORUS; + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerRadius = innerRadius; + shape.outerRadius = outerRadius; + + shape.coneOffset = 0; + shape.coneAngle = 360; + + shape.edgeBias = edgeBias; + + return shape; +} + +EmissionShape ParticleSystem::createConeTorusShape(Vec2 pos, + float innerRadius, + float outerRadius, + float offset, + float angle, + float edgeBias) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::TORUS; + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerRadius = innerRadius; + shape.outerRadius = outerRadius; + + shape.coneOffset = offset; + shape.coneAngle = angle; + + shape.edgeBias = edgeBias; + + return shape; +} + +void ParticleSystem::setLifeAnimation(bool enabled) +{ + if (enabled && !allocAnimationMem()) + return; + + if (!enabled) + deallocAnimationMem(); + + _isLifeAnimated = enabled; + _isEmitterAnimated = false; + _isLoopAnimated = false; +} + +void ParticleSystem::setEmitterAnimation(bool enabled) +{ + if (enabled && !allocAnimationMem()) + return; + + if (!enabled) + deallocAnimationMem(); + + _isEmitterAnimated = enabled; + _isLifeAnimated = false; + _isLoopAnimated = false; +} + +void ParticleSystem::setLoopAnimation(bool enabled) +{ + if (enabled && !allocAnimationMem()) + return; + + if (!enabled) + deallocAnimationMem(); + + _isLoopAnimated = enabled; + _isEmitterAnimated = false; + _isLifeAnimated = false; +} + +void ParticleSystem::resetAnimationIndices() +{ + _animIndexCount = 0; + _animationIndices.clear(); +} + +void ParticleSystem::resetAnimationDescriptors() +{ + _animations.clear(); + _randomAnimations.clear(); +} + +void ParticleSystem::setMultiAnimationRandom() +{ + _randomAnimations.clear(); + for (auto& a : _animations) + _randomAnimations.push_back(a.first); +} + +void ParticleSystem::setAnimationIndicesAtlas() +{ + // VERTICAL + if (_texture->getPixelsHigh() > _texture->getPixelsWide()) + { + setAnimationIndicesAtlas(_texture->getPixelsWide(), ParticleSystem::TexAnimDir::VERTICAL); + return; + } + + // HORIZONTAL + if (_texture->getPixelsWide() > _texture->getPixelsHigh()) + { + setAnimationIndicesAtlas(_texture->getPixelsHigh(), ParticleSystem::TexAnimDir::HORIZONTAL); + return; + } + + CCASSERT(false, "Couldn't figure out the atlas size and direction."); +} + +void ParticleSystem::setAnimationIndicesAtlas(unsigned int unifiedCellSize, TexAnimDir direction) +{ + CCASSERT(unifiedCellSize > 0, "A cell cannot have a size of zero."); + + resetAnimationIndices(); + + auto texWidth = _texture->getPixelsWide(); + auto texHeight = _texture->getPixelsHigh(); + + switch (direction) + { + case TexAnimDir::VERTICAL: + { + for (short i = 0; i < short(texHeight / unifiedCellSize); i++) + { + Rect frame{}; + + frame.origin.x = 0; + frame.origin.y = unifiedCellSize * i; + + frame.size.x = texWidth; + frame.size.y = unifiedCellSize; + + addAnimationIndex(_animIndexCount, frame); + } + + break; + }; + case TexAnimDir::HORIZONTAL: + { + for (short i = 0; i < short(texWidth / unifiedCellSize); i++) + { + Rect frame{}; + + frame.origin.x = unifiedCellSize * i; + frame.origin.y = 0; + + frame.size.x = unifiedCellSize; + frame.size.y = texHeight; + + addAnimationIndex(_animIndexCount, frame); + } + + break; + }; + } +} + +bool ParticleSystem::addAnimationIndex(std::string_view frameName) +{ + return addAnimationIndex(_animIndexCount, frameName); +} + +bool ParticleSystem::addAnimationIndex(unsigned short index, std::string_view frameName) +{ + auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(frameName); + + if (frame) + return addAnimationIndex(index, frame); + return false; +} + +bool ParticleSystem::addAnimationIndex(cocos2d::SpriteFrame* frame) +{ + return addAnimationIndex(_animIndexCount, frame); +} + +bool ParticleSystem::addAnimationIndex(unsigned short index, cocos2d::SpriteFrame* frame) +{ + if (frame) + { + auto rect = frame->getRectInPixels(); + rect.size.x = frame->getOriginalSizeInPixels().x; + rect.size.y = frame->getOriginalSizeInPixels().y; + return addAnimationIndex(index, rect, frame->isRotated()); + } + return false; +} + +bool ParticleSystem::addAnimationIndex(unsigned short index, cocos2d::Rect rect, bool rotated) +{ + auto iter = _animationIndices.find(index); + if (iter == _animationIndices.end()) + { + iter = _animationIndices.emplace(index, ParticleFrameDescriptor{}).first; + _animIndexCount++; + } + + auto& desc = iter->second; + desc.rect = rect; + desc.isRotated = rotated; + + return true; +} + +void ParticleSystem::simulate(float seconds, float frameRate) +{ + seconds = seconds == SIMULATION_USE_PARTICLE_LIFETIME ? getLife() + getLifeVar() : seconds; + frameRate = frameRate == SIMULATION_USE_GAME_ANIMATION_INTERVAL + ? 1.0F / Director::getInstance()->getAnimationInterval() + : frameRate; + auto delta = 1.0F / frameRate; + if (seconds > delta) + { + while (seconds > 0.0F) + { + this->update(delta); + seconds -= delta; + } + this->update(seconds); + } + else + this->update(seconds); +} + +void ParticleSystem::resimulate(float seconds, float frameRate) +{ + this->resetSystem(); + this->simulate(seconds, frameRate); +} + void ParticleSystem::onEnter() { Node::onEnter(); @@ -834,10 +1536,7 @@ void ParticleSystem::resetSystem() { _isActive = true; _elapsed = 0; - for (int i = 0; i < _particleCount; ++i) - { - _particleData.timeToLive[i] = 0.0f; - } + std::fill_n(_particleData.timeToLive, _particleCount, 0.0F); } bool ParticleSystem::isFull() @@ -848,8 +1547,34 @@ bool ParticleSystem::isFull() // ParticleSystem - MainLoop void ParticleSystem::update(float dt) { + // don't process particles nor update gl buffer when this node is invisible. + if (!_visible) + return; + CC_PROFILER_START_CATEGORY(kProfilerCategoryParticles, "CCParticleSystem - update"); + if (_componentContainer && !_componentContainer->isEmpty()) + { + _componentContainer->visit(dt); + } + + if (_fixedFPS != 0) + { + _fixedFPSDelta += dt; + if (_fixedFPSDelta < 1.0F / _fixedFPS) + { + updateParticleQuads(); + _transformSystemDirty = false; + CC_PROFILER_STOP_CATEGORY(kProfilerCategoryParticles, "CCParticleSystem - update"); + return; + } + dt = _fixedFPSDelta; + _fixedFPSDelta = 0.0F; + } + + float pureDt = dt; + dt *= _timeScale; + if (_isActive && _emissionRate) { float rate = 1.0f / _emissionRate; @@ -859,8 +1584,7 @@ void ParticleSystem::update(float dt) if (_particleCount < totalParticles) { _emitCounter += dt; - if (_emitCounter < 0.f) - _emitCounter = 0.f; + _emitCounter = MAX(0.0F, _emitCounter); } int emitCount = MIN(totalParticles - _particleCount, _emitCounter / rate); @@ -876,12 +1600,104 @@ void ParticleSystem::update(float dt) } } + // The reason for using for-loops separately for every property is because + // When the processor needs to read from or write to a location in memory, + // it first checks whether a copy of that data is in the cpu's cache. + // And wether if every property's memory of the particle system is continuous, + // for the purpose of improving cache hit rate, we should process only one property in one for-loop. + // It was proved to be effective especially for low-end devices. { for (int i = 0; i < _particleCount; ++i) { _particleData.timeToLive[i] -= dt; } + if (_isOpacityFadeInAllocated) + { + for (int i = 0; i < _particleCount; ++i) + { + _particleData.opacityFadeInDelta[i] += dt; + _particleData.opacityFadeInDelta[i] = + MIN(_particleData.opacityFadeInDelta[i], _particleData.opacityFadeInLength[i]); + } + } + + if (_isScaleInAllocated) + { + for (int i = 0; i < _particleCount; ++i) + { + _particleData.scaleInDelta[i] += dt; + _particleData.scaleInDelta[i] = MIN(_particleData.scaleInDelta[i], _particleData.scaleInLength[i]); + } + } + + if (_isLifeAnimated || _isEmitterAnimated || _isLoopAnimated) + { + if (_isEmitterAnimated && !_animations.empty()) + { + for (int i = 0; i < _particleCount; ++i) + { + _particleData.animTimeDelta[i] += (_animationTimescaleInd ? pureDt : dt); + if (_particleData.animTimeDelta[i] > _particleData.animTimeLength[i]) + { + auto& anim = _animations.at(_particleData.animIndex[i]); + float percent = _rng.float01(); + percent = anim.reverseIndices ? 1.0F - percent : percent; + + _particleData.animCellIndex[i] = anim.animationIndices[MIN( + percent * anim.animationIndices.size(), anim.animationIndices.size() - 1)]; + _particleData.animTimeDelta[i] = 0; + } + } + } + if (_isLifeAnimated && _animations.empty()) + { + for (int i = 0; i < _particleCount; ++i) + { + float percent = (_particleData.totalTimeToLive[i] - _particleData.timeToLive[i]) / + _particleData.totalTimeToLive[i]; + percent = _isAnimationReversed ? 1.0F - percent : percent; + _particleData.animCellIndex[i] = + (unsigned short)MIN(percent * _animIndexCount, _animIndexCount - 1); + } + } + if (_isLifeAnimated && !_animations.empty()) + { + for (int i = 0; i < _particleCount; ++i) + { + auto& anim = _animations.at(_particleData.animIndex[i]); + + float percent = (_particleData.totalTimeToLive[i] - _particleData.timeToLive[i]) / + _particleData.totalTimeToLive[i]; + percent = (!!_isAnimationReversed != !!anim.reverseIndices) ? 1.0F - percent : percent; + percent = MAX(0.0F, percent); + + _particleData.animCellIndex[i] = anim.animationIndices[MIN(percent * anim.animationIndices.size(), + anim.animationIndices.size() - 1)]; + } + } + if (_isLoopAnimated && !_animations.empty()) + { + for (int i = 0; i < _particleCount; ++i) + { + auto& anim = _animations.at(_particleData.animIndex[i]); + + _particleData.animTimeDelta[i] += (_animationTimescaleInd ? pureDt : dt); + if (_particleData.animTimeDelta[i] >= _particleData.animTimeLength[i]) + _particleData.animTimeDelta[i] = 0; + + float percent = _particleData.animTimeDelta[i] / _particleData.animTimeLength[i]; + percent = anim.reverseIndices ? 1.0F - percent : percent; + percent = MAX(0.0F, percent); + + _particleData.animCellIndex[i] = anim.animationIndices[MIN(percent * anim.animationIndices.size(), + anim.animationIndices.size() - 1)]; + } + } + if (_isLoopAnimated && _animations.empty()) + std::fill_n(_particleData.animTimeDelta, _particleCount, 0); + } + for (int i = 0; i < _particleCount; ++i) { if (_particleData.timeToLive[i] <= 0.0f) @@ -952,12 +1768,6 @@ void ParticleSystem::update(float dt) } else { - // Why use so many for-loop separately instead of putting them together? - // When the processor needs to read from or write to a location in memory, - // it first checks whether a copy of that data is in the cache. - // And every property's memory of the particle system is continuous, - // for the purpose of improving cache hit rate, we should process only one property in one for-loop AFAP. - // It was proved to be effective especially for low-end machine. for (int i = 0; i < _particleCount; ++i) { _particleData.modeB.angle[i] += _particleData.modeB.degreesPerSecond[i] * dt; @@ -1015,7 +1825,7 @@ void ParticleSystem::update(float dt) _transformSystemDirty = false; } - // only update gl buffer when visible + // update and send gl buffer only when this node is visible. if (_visible && !_batchNode) { postStep(); @@ -1276,6 +2086,47 @@ bool ParticleSystem::isActive() const return _isActive; } +void ParticleSystem::useHSV(bool hsv) +{ + if (hsv && !allocHSVMem()) + return; + + if (!hsv) + deallocHSVMem(); +}; + +void ParticleSystem::setSpawnFadeIn(float time) +{ + if (time != 0.0F && !allocOpacityFadeInMem()) + return; + + _spawnFadeIn = time; +} + +void ParticleSystem::setSpawnFadeInVar(float time) +{ + if (time != 0.0F && !allocOpacityFadeInMem()) + return; + + _spawnFadeInVar = time; +} + +void ParticleSystem::setSpawnScaleIn(float time) +{ + if (time != 0.0F && !allocScaleInMem()) + return; + + _spawnScaleIn = time; +} + +void ParticleSystem::setSpawnScaleInVar(float time) +{ + if (time != 0.0F && !allocScaleInMem()) + return; + + _spawnScaleInVar = time; +} + int ParticleSystem::getTotalParticles() const { return _totalParticles; @@ -1386,4 +2237,143 @@ void ParticleSystem::resumeEmissions() _paused = false; } -NS_CC_END +float ParticleSystem::getFixedFPS() +{ + return _fixedFPS; +} + +void ParticleSystem::setFixedFPS(float frameRate) +{ + _fixedFPS = frameRate; +} + +float ParticleSystem::getTimeScale() +{ + return _timeScale; +} + +void ParticleSystem::setTimeScale(float scale) +{ + _timeScale = scale; +} + +static ParticleEmissionMaskCache* emissionMaskCache; + +ParticleEmissionMaskCache* ParticleEmissionMaskCache::getInstance() +{ + if (emissionMaskCache == nullptr) + { + emissionMaskCache = new ParticleEmissionMaskCache(); + return emissionMaskCache; + } + return emissionMaskCache; +} + +void ParticleEmissionMaskCache::bakeEmissionMask(std::string_view maskId, + std::string_view texturePath, + float alphaThreshold, + bool inverted, + int inbetweenSamples) +{ + auto img = new Image(); + img->Image::initWithImageFile(texturePath); + img->autorelease(); + + CCASSERT(img, "image texture was nullptr."); + bakeEmissionMask(maskId, img, alphaThreshold, inverted, inbetweenSamples); +} + +void ParticleEmissionMaskCache::bakeEmissionMask(std::string_view maskId, + Image* imageTexture, + float alphaThreshold, + bool inverted, + int inbetweenSamples) +{ + auto img = imageTexture; + CCASSERT(img, "image texture was nullptr."); + CCASSERT(img->hasAlpha(), "image data should contain an alpha channel."); + + vector points; + + auto data = img->getData(); + auto w = img->getWidth(); + auto h = img->getHeight(); + + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + if (inbetweenSamples > 1) + { + float a = data[(y * w + x) * 4 + 3] / 255.0F; + if (a >= alphaThreshold && !inverted) + for (float i = 0; i < 1.0F; i += 1.0F / inbetweenSamples) + points.push_back({float(x + i), float(h - y + i)}); + if (a < alphaThreshold && inverted) + for (float i = 0; i < 1.0F; i += 1.0F / inbetweenSamples) + points.push_back({float(x + i), float(h - y + i)}); + } + else + { + float a = data[(y * w + x) * 4 + 3] / 255.0F; + if (a >= alphaThreshold && !inverted) + points.push_back({float(x), float(h - y)}); + if (a < alphaThreshold && inverted) + points.push_back({float(x), float(h - y)}); + } + } + + auto fourccId = utils::fourccValue(maskId); + + auto iter = this->masks.find(fourccId); + if (iter == this->masks.end()) + iter = this->masks.emplace(fourccId, ParticleEmissionMaskDescriptor{}).first; + + ParticleEmissionMaskDescriptor desc; + desc.size = {float(w), float(h)}; + desc.points = std::move(points); + + iter->second = desc; + + CCLOG("Particle emission mask '%lu' baked (%dx%d), %d samples generated taking %.2fmb of memory.", + (unsigned long)htonl(fourccId), w, h, desc.points.size(), desc.points.size() * 8 / 1e+6); +} + +const ParticleEmissionMaskDescriptor& ParticleEmissionMaskCache::getEmissionMask(uint32_t fourccId) +{ + auto iter = this->masks.find(fourccId); + if (iter == this->masks.end()) + { + iter = this->masks.emplace(fourccId, ParticleEmissionMaskDescriptor{}).first; + iter->second.size = {float(1), float(1)}; + iter->second.points = {{0, 0}}; + return iter->second; + } + return iter->second; +} + +const ParticleEmissionMaskDescriptor& ParticleEmissionMaskCache::getEmissionMask(std::string_view maskId) +{ + auto fourccId = utils::fourccValue(maskId); + + auto iter = this->masks.find(fourccId); + if (iter == this->masks.end()) + { + iter = this->masks.emplace(fourccId, ParticleEmissionMaskDescriptor{}).first; + iter->second.size = {float(1), float(1)}; + iter->second.points = {{0, 0}}; + return iter->second; + } + return iter->second; +} + +void ParticleEmissionMaskCache::removeMask(std::string_view maskId) +{ + this->masks.erase(utils::fourccValue(maskId)); +} + +void ParticleEmissionMaskCache::removeAllMasks() +{ + this->masks.clear(); +} + +NS_CC_END \ No newline at end of file diff --git a/core/2d/CCParticleSystem.h b/core/2d/CCParticleSystem.h index 4cd110e10f..f8f94820cc 100644 --- a/core/2d/CCParticleSystem.h +++ b/core/2d/CCParticleSystem.h @@ -32,6 +32,9 @@ THE SOFTWARE. #include "base/CCProtocols.h" #include "2d/CCNode.h" #include "base/CCValue.h" +#include "2d/CCSpriteFrame.h" +#include "2d/CCSpriteFrameCache.h" +#include "math/RngSeed.hpp" NS_CC_BEGIN @@ -52,6 +55,77 @@ struct particle_point float y; }; +/** + * Particle emission shapes. + * Current supported shapes are Point, Rectangle, RectangularTorus, Circle, Torus + * @since adxe-1.0.0b8 + */ +enum class EmissionShapeType +{ + POINT, + RECT, + RECTTORUS, + CIRCLE, + TORUS, + ALPHA_MASK +}; + +/** + * Particle emission mask descriptor. + * @since adxe-1.0.0b8 + */ +struct ParticleEmissionMaskDescriptor +{ + Vec2 size; + std::vector points; +}; + +/** + * Particle emission shapes. + * Current supported shapes are Point, Rectangle, RectangularTorus, Circle, Torus, Cone, Cone Torus + * @since adxe-1.0.0b8 + */ +struct EmissionShape +{ + EmissionShapeType type; + + float x; + float y; + + float innerWidth; + float innerHeight; + float outerWidth; + float outerHeight; + + float innerRadius; + float outerRadius; + float coneOffset; + float coneAngle; + float edgeBias; + + uint32_t fourccId; +}; + +/** @struct ParticleAnimationDescriptor +Structure that contains animation description +*/ +struct ParticleAnimationDescriptor +{ + float animationSpeed; + float animationSpeedVariance; + std::vector animationIndices; + bool reverseIndices; +}; + +/** @struct ParticleFrameDescriptor +Structure that contains frame description +*/ +struct ParticleFrameDescriptor +{ + cocos2d::Rect rect; + bool isRotated; +}; + class CC_DLL ParticleData { public: @@ -70,11 +144,27 @@ public: float* deltaColorB; float* deltaColorA; + float* hue; + float* sat; + float* val; + + float* opacityFadeInDelta; + float* opacityFadeInLength; + + float* scaleInDelta; + float* scaleInLength; + float* size; float* deltaSize; float* rotation; + float* staticRotation; float* deltaRotation; + float* totalTimeToLive; float* timeToLive; + float* animTimeDelta; + float* animTimeLength; + unsigned short* animIndex; + unsigned short* animCellIndex; unsigned int* atlasIndex; //! Mode A: gravity, direction, radial accel, tangential accel @@ -118,13 +208,41 @@ public: deltaColorB[p1] = deltaColorB[p2]; deltaColorA[p1] = deltaColorA[p2]; - size[p1] = size[p2]; - deltaSize[p1] = deltaSize[p2]; + if (hue && sat && val) + { + hue[p1] = hue[p2]; + sat[p1] = sat[p2]; + val[p1] = val[p2]; + } - rotation[p1] = rotation[p2]; - deltaRotation[p1] = deltaRotation[p2]; + if (opacityFadeInDelta && opacityFadeInLength) + { + opacityFadeInDelta[p1] = opacityFadeInDelta[p2]; + opacityFadeInLength[p1] = opacityFadeInLength[p2]; + } - timeToLive[p1] = timeToLive[p2]; + if (scaleInDelta && scaleInLength) + { + scaleInDelta[p1] = scaleInDelta[p2]; + scaleInLength[p1] = scaleInLength[p2]; + } + + size[p1] = size[p2]; + deltaSize[p1] = deltaSize[p2]; + rotation[p1] = rotation[p2]; + staticRotation[p1] = staticRotation[p2]; + deltaRotation[p1] = deltaRotation[p2]; + + totalTimeToLive[p1] = totalTimeToLive[p2]; + timeToLive[p1] = timeToLive[p2]; + + if (animTimeDelta && animTimeLength && animIndex && animCellIndex) + { + animTimeDelta[p1] = animTimeDelta[p2]; + animTimeLength[p1] = animTimeLength[p2]; + animIndex[p1] = animIndex[p2]; + animCellIndex[p1] = animCellIndex[p2]; + } atlasIndex[p1] = atlasIndex[p2]; @@ -140,6 +258,77 @@ public: } }; +/** + * Particle emission mask cache. + * @since adxe-1.0.0b8 + */ +class CC_DLL ParticleEmissionMaskCache : public cocos2d::Ref +{ +public: + static ParticleEmissionMaskCache* getInstance(); + + /** Bakes a particle emission mask from texture data on cpu and stores it in memory by it's name. + * If the mask already exists then it will be overwritten. + * + * @param maskId The id of the mask, FOURCC starts with '#', such as "#abcd" + * @param texturePath Path of the texture that holds alpha data. + * @param alphaThreshold The threshold at which pixels are picked, If a pixel's alpha channel is greater than + * alphaThreshold then it will be picked. + * @param inverted Inverts the pick condition so that If a pixel's alpha channel is lower than alphaThreshold then + * it will be picked. + * @param inbetweenSamples How many times should pixels be filled inbetween, this value should be increased If + * you're planning to scale the emission shape up. WARNING: it will use more memory. + */ + void bakeEmissionMask(std::string_view maskId, + std::string_view texturePath, + float alphaThreshold = 0.5F, + bool inverted = false, + int inbetweenSamples = 1); + + /** Bakes a particle emission mask from texture data on cpu and stores it in memory by it's name. + * If the mask already exists then it will be overwritten. + * + * @param maskId The id of the mask, FOURCC starts with '#', such as "#abcd" + * @param imageTexture Image object containing texture data with alpha channel. + * @param alphaThreshold The threshold at which pixels are picked, If a pixel's alpha channel is greater than + * alphaThreshold then it will be picked. + * @param inverted Inverts the pick condition so that If a pixel's alpha channel is lower than alphaThreshold then + * it will be picked. + * @param inbetweenSamples How many times should pixels be filled inbetween, this value should be increased If + * you're planning to scale the emission shape up. WARNING: it will use more memory. + */ + void bakeEmissionMask(std::string_view maskId, + Image* imageTexture, + float alphaThreshold = 0.5F, + bool inverted = false, + int inbetweenSamples = 1); + + /** Returns a baked mask with the specified name if it exists. otherwise, it will return a dummy mask. + * + * @param fourccId The unsigned integer id of the mask. + */ + const ParticleEmissionMaskDescriptor& getEmissionMask(uint32_t fourccId); + + /** Returns a baked mask with the specified name if it exists. otherwise, it will return a dummy mask. + * + * @param maskId The id of the mask, FOURCC starts with '#', such as "#abcd" + */ + const ParticleEmissionMaskDescriptor& getEmissionMask(std::string_view maskId); + + /** Removes a baked mask and releases the data from memory with the specified name if it exists. + * + * @param maskId The id of the mask, FOURCC starts with '#', such as "#abcd" + */ + void removeMask(std::string_view maskId); + + /** Remove all baked masks and releases their data from memory. */ + void removeAllMasks(); + +private: + std::unordered_map masks; + +}; + // typedef void (*CC_UPDATE_PARTICLE_IMP)(id, SEL, tParticle*, Vec2); class Texture2D; @@ -188,7 +377,6 @@ emitter.startSpin = 0; @endcode */ - class CC_DLL ParticleSystem : public Node, public TextureProtocol, public PlayableProtocol { public: @@ -202,7 +390,7 @@ public: }; /** PositionType - Possible types of particle positions. + Types of particle positioning. * @js cc.ParticleSystem.TYPE_FREE */ enum class PositionType @@ -216,6 +404,17 @@ public: }; + /** TexAnimDir + Texture animation direction for the particles. + */ + enum class TexAnimDir + { + VERTICAL, /** texture coordinates are read top to bottom within the texture */ + + HORIZONTAL, /** texture coordinates are read left to right within the texture */ + + }; + //* @enum enum { @@ -227,6 +426,12 @@ public: /** The starting radius of the particle is equal to the ending radius. */ START_RADIUS_EQUAL_TO_END_RADIUS = -1, + + /** The simulation's seconds are set to the particles' lifetime specified inclusive of variant. */ + SIMULATION_USE_PARTICLE_LIFETIME = -1, + + /** The simulation's framerate is set to the animation interval specified in director. */ + SIMULATION_USE_GAME_ANIMATION_INTERVAL = -1, }; /** Creates an initializes a ParticleSystem from a plist file. @@ -251,8 +456,25 @@ public: */ static Vector& getAllParticleSystems(); +protected: + bool allocAnimationMem(); + void deallocAnimationMem(); + bool _isAnimAllocated; + + bool allocHSVMem(); + void deallocHSVMem(); + bool _isHSVAllocated; + + bool allocOpacityFadeInMem(); + void deallocOpacityFadeInMem(); + bool _isOpacityFadeInAllocated; + + bool allocScaleInMem(); + void deallocScaleInMem(); + bool _isScaleInAllocated; + public: - void addParticles(int count); + void addParticles(int count, int animationIndex = -1, int animationCellIndex = -1); void stopSystem(); /** Kill all living particles. @@ -658,6 +880,58 @@ public: */ void setEndColorVar(const Color4F& color) { _endColorVar = color; } + /** Sets wether to use HSV color system. + * WARNING: becareful when using HSV with too many particles because it's expensive. + * + * @param hsv Use HSV color system. + */ + void useHSV(bool hsv); + bool isHSV() { return _isHSVAllocated; }; + + /** Gets the hue of each particle. + * + * @return The hue of each particle. + */ + float getHue() const { return _hsv.h; } + /** Sets the hue of each particle. + * + * @param hsv The hue color of each particle. + */ + void setHue(float hue) { _hsv.h = hue; } + + /** Gets the hue variance of each particle. + * + * @return The hue variance of each particle. + */ + float getHueVar() const { return _hsvVar.h; } + /** Sets the hue variance of each particle. + * + * @param hsv The hue variance color of each particle. + */ + void setHueVar(float hue) { _hsvVar.h = hue; } + + /** Gets the HSV color of each particle. + * + * @return The HSV color of each particle. + */ + const HSV& getHSV() const { return _hsv; } + /** Sets the HSV color of each particle. + * + * @param hsv The HSV color of each particle. + */ + void setHSV(const HSV& hsv) { _hsv = hsv; } + + /** Gets the HSV color variance of each particle. + * + * @return The HSV color variance of each particle. + */ + const HSV& getHSVVar() const { return _hsvVar; } + /** Sets the HSV color variance of each particle. + * + * @param hsv The HSV color variance of each particle. + */ + void setHSVVar(const HSV& hsv) { _hsvVar = hsv; } + /** Gets the start spin of each particle. * * @return The start spin of each particle. @@ -702,6 +976,88 @@ public: */ void setEndSpinVar(float endSpinVar) { _endSpinVar = endSpinVar; } + /** Gets the spawn angle of each particle + * + * @return The angle in degrees of each particle. + */ + float getSpawnAngle() { return _spawnAngle; } + /** Sets the spawn angle of each particle + * + * @param angle The angle in degrees of each particle. + */ + void setSpawnAngle(float angle) { _spawnAngle = angle; } + + /** Sets the spawn angle variance of each particle. + * + * @return The angle variance in degrees of each particle. + */ + float getSpawnAngleVar() { return _spawnAngleVar; } + /** Sets the spawn angle variance of each particle. + * + * @param angle The angle variance in degrees of each particle. + */ + void setSpawnAngleVar(float angle) { _spawnAngleVar = angle; } + + /** Gets the spawn opacity fade in time of each particle. + * Particles have the ability to spawn while having 0 opacity and gradually start going to 255 opacity with a + specified time. + + * @return The spawn opacity fade in time in seconds. + */ + float getSpawnFadeIn() { return _spawnFadeIn; } + /** Sets the spawn opacity fade in time of each particle when it's created. + * Particles have the ability to spawn while having 0 opacity and gradually start going to 255 opacity with a + * specified time. + * + * @param time The spawn opacity fade in time in seconds. + */ + void setSpawnFadeIn(float time); + + /** Gets the spawn opacity fade in time variance of each particle. + * Particles have the ability to spawn while having 0 opacity and gradually start going to 255 opacity with a + * specified time. + * + * @return The spawn opacity fade in time variance in seconds. + */ + float getSpawnFadeInVar() { return _spawnFadeInVar; } + /** Sets the spawn opacity fade in time variance of each particle when it's created. + * Particles have the ability to spawn while having 0 opacity and gradually start going to 255 opacity with a + * specified time. + * + * @param time The spawn opacity fade in time variance in seconds. + */ + void setSpawnFadeInVar(float time); + + /** Gets the spawn scale fade in time of each particle. + * Particles have the ability to spawn while having 0.0 size and gradually start going to 1.0 size with a specified + * time. + * + * @return The spawn opacity fade in time in seconds. + */ + float getSpawnScaleIn() { return _spawnScaleIn; } + /** Sets the spawn scale fade in time of each particle when it's created. + * Particles have the ability to spawn while having 0.0 size and gradually start going to 1.0 size with a specified + * time. + * + * @param time The spawn opacity fade in time in seconds. + */ + void setSpawnScaleIn(float time); + + /** Gets the spawn scale fade in time variance of each particle. + * Particles have the ability to spawn while having 0.0 size and gradually start going to 1.0 size with a specified + * time. + * + * @return The spawn opacity fade in time variance in seconds. + */ + float getSpawnScaleInVar() { return _spawnScaleInVar; } + /** Sets the spawn scale fade in time variance of each particle when it's created. + * Particles have the ability to spawn while having 0.0 size and gradually start going to 1.0 size with a specified + * time. + * + * @param time The spawn opacity fade in time variance in seconds. + */ + void setSpawnScaleInVar(float time); + /** Gets the emission rate of the particles. * * @return The emission rate of the particles. @@ -728,6 +1084,266 @@ public: void setOpacityModifyRGB(bool opacityModifyRGB) override { _opacityModifyRGB = opacityModifyRGB; } bool isOpacityModifyRGB() const override { return _opacityModifyRGB; } + /** Enables or disables tex coord animations that are set based on particle life. */ + void setLifeAnimation(bool enabled); + + /** Enables or disables tex coord animations that are set by the emitter randomly when a particle is emitted. */ + void setEmitterAnimation(bool enabled); + + /** Enables or disables tex coord animations that are used to make particles play a sequence forever until they die + */ + void setLoopAnimation(bool enabled); + + bool isLifeAnimated() { return _isLifeAnimated; } + bool isEmitterAnimated() { return _isEmitterAnimated; } + bool isLoopAnimated() { return _isLoopAnimated; } + + /** Gets the total number of indices. + * + * @return The size of the list holding animation indices. + */ + int getTotalAnimationIndices() { return _animIndexCount; } + + /** Sets wether to start from first cell and go forwards (normal) or last cell and go backwards (reversed) */ + void setAnimationReverse(bool reverse) { _isAnimationReversed = reverse; } + bool isAnimationReversed() { return _isAnimationReversed; } + + /** Resets the count of indices to 0 and empties the animation index array */ + void resetAnimationIndices(); + + /** Empties the container of animation descriptors */ + void resetAnimationDescriptors(); + + /** Choose what animation descriptors are to be selected at random for particles. + * This function should be called after you've inserted/overwritten any animation descriptors. + * + * @param animations Array of specific indices of animations to play at random + */ + void setMultiAnimationRandomSpecific(const std::vector& animations) + { + _randomAnimations = animations; + }; + + /** Choose ALL animation descriptors to be selected at random for particles. + * This function should be called after you've inserted/overwritten any animation descriptors. + */ + void setMultiAnimationRandom(); + + /** Add all particle animation indices based on cells size and direction spicified using a texture atlas. + * will erase the array and add new indices from the atlas. + * This function will automatically figure out your atlas cell size and direction for you! thank her later :) */ + void setAnimationIndicesAtlas(); + + /** Add all particle animation indices based on cell size and direction spicified if the method of rendering + * preferred is texture atlas. will erase the array and add new indices from the atlas. + * + * @param unifiedCellSize The size of cell unified. + * @param direction What direction is the atlas + */ + void setAnimationIndicesAtlas(unsigned int unifiedCellSize, TexAnimDir direction = TexAnimDir::HORIZONTAL); + + /** Add a particle animation index based on tex coords spicified using a sprite frame. + * The index is automatically incremented on each addition. + * + * @param frameName SpriteFrame name to search for + * + * @return Returns true of the index was successfully found and added. Otherwise, false + */ + bool addAnimationIndex(std::string_view frameName); + + /** Add a particle animation index based on tex coords spicified using a sprite frame. + * + * @param index Index id to add the frame to or override it with the new frame + * @param frameName SpriteFrame name to search for + * + * @return Returns true of the index was successfully found and added. Otherwise, false + */ + bool addAnimationIndex(unsigned short index, std::string_view frameName); + + /** Add a particle animation index based on tex coords spicified using a sprite frame. + * The index is automatically incremented on each addition. + * + * @param frame SpriteFrame containting data about tex coords + * + * @return Returns true of the index was successfully found and added. Otherwise, false + */ + bool addAnimationIndex(cocos2d::SpriteFrame* frame); + + /** Add a particle animation index based on tex coords spicified using a sprite frame. + * you can specify which index you want to override in this function + * + * @param index Index id to add the frame to or override it with the new frame + * @param frame SpriteFrame containting data about tex coords + * + * @return Returns true of the index was successfully found and added. Otherwise, false + */ + bool addAnimationIndex(unsigned short index, cocos2d::SpriteFrame* frame); + + /** Add a particle animation index based on tex coords spicified. + * you can specify which index you want to override in this function + * + * @param index Index id to add the frame to or override it with the new rect + * @param rect Rect containting data about tex coords in pixels + * @param rotated Not implemented. + * + * @return Returns true of the index was successfully found and added. Otherwise, false + */ + bool addAnimationIndex(unsigned short index, cocos2d::Rect rect, bool rotated = false); + + /** You can specify what rect is used if an index in an animation descriptor wasn't found. + * + * @param rect Rect containting data about tex coords in pixels + */ + void setRectForUndefinedIndices(cocos2d::Rect rect) { _undefinedIndexRect = rect; }; + + /** Add a particle animation descriptor with an index. + * + * @param indexOfDescriptor Index of the animation to be added, adding to the same index will just override the + * pervious animation descriptor + * @param time length of the animation in seconds + * @param timeVariance Time randomly selected for each different particle added on the animation length + * @param indices An array of the indicies + * @param reverse Should the animation indicies be played backwards? (default: false) + */ + void setAnimationDescriptor(unsigned short indexOfDescriptor, + float time, + float timeVariance, + const std::vector& indices, + bool reverse = false); + + /** Add a particle animation descriptor with the index 0. + * + * @param indices An array of the indicies + * @param reverse Should the animation indicies be played backwards? (default: false) + */ + void setAnimationDescriptor(const std::vector& indices, bool reverse = false) + { + setAnimationDescriptor(0, 0, 0, indices, reverse); + }; + + /** Sets wether the animation descriptors should follow the time scale of the system or not. + * + * @param independent Should the animation descriptor speeds be played independently? (default: false) + */ + void setAnimationSpeedTimescaleIndependent(bool independent) { _animationTimescaleInd = independent; }; + bool isAnimationSpeedTimescaleIndependent() { return _animationTimescaleInd; }; + + /** Sets wether to use emission shapes for this particle system or not */ + void setEmissionShapes(bool enabled) { _isEmissionShapes = enabled; } + bool isEmissionShapes() { return _isEmissionShapes; } + + /** Resets the count of emission shapes to 0 and empties the emission shapes array */ + void resetEmissionShapes(); + + /** Adds an emission shape to the system. + * The index is automatically incremented on each addition. + * + * @param shape Shape descriptor object. + */ + void addEmissionShape(EmissionShape shape); + + /** Updates an existing emission shape or adds it. + * @param index index of the shape descriptor. + * @param shape Shape descriptor object. + */ + void setEmissionShape(unsigned short index, EmissionShape shape); + + /** Adds an emission shape of type mask to the system. + * The mask should be added using the ParticleEmissionMaskCache class. + * + * @param maskId The id of the mask, FOURCC starts with '#', such as "#abcd" + * @param pos Position of the emission shape in local space. + * @param overrideSize Size of the emission mask in pixels, leave ZERO to use texture size. + * @param scale Scale of the emission mask, the size will be multiplied by the specified scale. + * @param angle Angle of the sampled points to be rotated in degrees. + */ + static EmissionShape createMaskShape(std::string_view maskId, Vec2 pos = Vec2::ZERO, Vec2 overrideSize = Vec2::ZERO, Vec2 scale = Vec2::ONE, float angle = 0.0F); + + /** Adds an emission shape of type point to the system. + * @param pos Position of the emission shape in local space. + */ + static EmissionShape createPointShape(Vec2 pos); + + /** Adds an emission shape of type Rectangle to the system. + * @param pos Position of the emission shape in local space. + * @param size Size of the rectangle. + */ + static EmissionShape createRectShape(Vec2 pos, Size size); + + /** Adds an emission shape of type Rectangular Torus to the system. + * @param pos Position of the emission shape in local space. + * @param innerSize Inner size offset of the rectangle. + * @param outerSize Outer size of the rectangle. + */ + static EmissionShape createRectTorusShape(Vec2 pos, Size innerSize, Size outerSize); + + /** Adds an emission shape of type Circle to the system. + * + * The default angle offset of the circle is 0 and the default angle of the circle is 360 + * + * @param pos Position of the emission shape in local space. + * @param radius Radius of the circle. + * @param edgeBias circle edge center bias value, If the value is greater than 1.0 then particles will bias + * towards the edge of the circle more often the greater the value is; If the value is lower than 1.0 then particles + * will bias towards the center of the circle more often the closer the value is to 0.0; If the value is exactly 1.0 + * then there will be no bias behaviour. + */ + static EmissionShape createCircleShape(Vec2 pos, float radius, float edgeBias = 1.0F); + + /** Adds an emission shape of type Cone to the system. + * + * The default angle offset of the circle is 0 and the default angle of the circle is 360 + * + * @param pos Position of the emission shape in local space. + * @param radius Radius of the circle. + * @param offset Cone offset angle in degrees. + * @param angle Cone angle in degrees. + * @param edgeBias circle edge center bias value, If the value is greater than 1.0 then particles will bias + * towards the edge of the circle more often the greater the value is; If the value is lower than 1.0 then particles + * will bias towards the center of the circle more often the closer the value is to 0.0; If the value is exactly 1.0 + * then there will be no bias behaviour. + */ + static EmissionShape createConeShape(Vec2 pos, + float radius, + float offset, + float angle, + float edgeBias = 1.0F); + + /** Adds an emission shape of type Torus to the system. + * + * The default angle offset of the torus is 0 and the default angle of the torus is 360 + * + * @param pos Position of the emission shape in local space. + * @param innerRadius Inner radius offset of the torus. + * @param outerRadius Outer radius of the torus. + * @param edgeBias torus edge center bias value, If the value is greater than 1.0 then particles will bias + * towards the edge of the torus more often the greater the value is; If the value is lower than 1.0 then particles + * will bias towards the center of the torus more often the closer the value is to 0.0; If the value is exactly 1.0 + * then there will be no bias behaviour. + */ + static EmissionShape createTorusShape(Vec2 pos, float innerRadius, float outerRadius, float edgeBias = 1.0F); + + /** Adds an emission shape of type Torus to the system. + * + * The default angle offset of the torus is 0 and the default angle of the torus is 360 + * + * @param pos Position of the emission shape in local space. + * @param innerRadius Inner radius offset of the torus. + * @param outerRadius Outer radius of the torus. + * @param offset Cone offset angle in degrees. + * @param angle Cone angle in degrees. + * @param edgeBias torus edge center bias value, If the value is greater than 1.0 then particles will bias + * towards the edge of the torus more often the greater the value is; If the value is lower than 1.0 then particles + * will bias towards the center of the torus more often the closer the value is to 0.0; If the value is exactly 1.0 + * then there will be no bias behaviour. + */ + static EmissionShape createConeTorusShape(Vec2 pos, + float innerRadius, + float outerRadius, + float offset, + float angle, + float edgeBias = 1.0F); + /** Gets the particles movement type: Free or Grouped. @since v0.8 * @@ -741,6 +1357,27 @@ public: */ void setPositionType(PositionType type) { _positionType = type; } + /** Advance the particle system and make it seem like it ran for this many seconds. + * + * @param seconds Seconds to advance. value of -1 means (SIMULATION_USE_PARTICLE_LIFETIME) + * @param frameRate Frame rate to run the simulation with (preferred: 30.0) The higher this value is the more + * accurate the simulation will be at the cost of performance. value of -1 means + * (SIMULATION_USE_GAME_ANIMATION_INTERVAL) + */ + void simulate(float seconds = SIMULATION_USE_PARTICLE_LIFETIME, + float frameRate = SIMULATION_USE_GAME_ANIMATION_INTERVAL); + + /** Resets the particle system and then advances the particle system and make it seem like it ran for this many + * seconds. The frame rate used for simulation accuracy is the director's animation interval. + * + * @param seconds Seconds to advance. value of -1 means (SIMULATION_USE_PARTICLE_LIFETIME) + * @param frameRate Frame rate to run the simulation with (preferred: 30.0) The higher this value is the more + * accurate the simulation will be at the cost of performance. value of -1 means + * (SIMULATION_USE_GAME_ANIMATION_INTERVAL) + */ + void resimulate(float seconds = SIMULATION_USE_PARTICLE_LIFETIME, + float frameRate = SIMULATION_USE_GAME_ANIMATION_INTERVAL); + // Overrides virtual void onEnter() override; virtual void onExit() override; @@ -812,12 +1449,35 @@ public: */ virtual bool isPaused() const; - /* Pause the emissions*/ + /* Pause the emissions */ virtual void pauseEmissions(); - /* UnPause the emissions*/ + /* Unpause the emissions */ virtual void resumeEmissions(); + /** Gets the fixed frame rate count of the particle system. + @return Fixed frame rate count of the particle system. + */ + virtual float getFixedFPS(); + + /** Sets the fixed frame rate count of the particle system. + * i.e. if the framerate is set to 30.0 while the refresh rate is greater than 30.0 then the particle system will + wait until it hits the 30.0 FPS mark. + * This is usefull for increasing performance or for creating old-school effects with it. + @param Fixed frame rate count of the particle system. (default: 0.0) + */ + virtual void setFixedFPS(float frameRate = 0.0F); + + /** Gets the time scale of the particle system. + @return Time scale of the particle system. + */ + virtual float getTimeScale(); + + /** Sets the time scale of the particle system. + @param Time scale of the particle system. (default: 1.0) + */ + virtual void setTimeScale(float scale = 1.0F); + protected: virtual void updateBlendFunc(); @@ -957,6 +1617,10 @@ protected: Color4F _endColor; /** end color variance of each particle */ Color4F _endColorVar; + /** hsv color of each particle */ + HSV _hsv; + /** hsv color variance of each particle */ + HSV _hsvVar; //* initial angle of each particle float _startSpin; //* initial angle of each particle @@ -965,6 +1629,18 @@ protected: float _endSpin; //* initial angle of each particle float _endSpinVar; + //* initial rotation of each particle + float _spawnAngle; + //* initial rotation of each particle + float _spawnAngleVar; + //* initial fade in time of each particle + float _spawnFadeIn; + //* initial fade in time variance of each particle + float _spawnFadeInVar; + //* initial scale in time of each particle + float _spawnScaleIn; + //* initial scale in time variance of each particle + float _spawnScaleInVar; /** emission rate of the particles */ float _emissionRate; /** maximum particles of the system */ @@ -975,9 +1651,37 @@ protected: BlendFunc _blendFunc; /** does the alpha value modify color */ bool _opacityModifyRGB; + /** is the particle system animated */ + bool _isLifeAnimated; + /** is the emitter particle system animated */ + bool _isEmitterAnimated; + /** is the emitter particle system animated */ + bool _isLoopAnimated; + /** variable keeping count of sprite frames or atlas indices added */ + int _animIndexCount; + /** wether to start from first or last when using life animation */ + bool _isAnimationReversed; + /** A map that stores particle animation index coords */ + std::unordered_map _animationIndices; + /** A map that stores particle animation descriptors */ + std::unordered_map _animations; + /** A vector that stores ids of animation descriptors that are choosen at random */ + std::vector _randomAnimations; + /** Wether the animation goes with the time scale of the system or is independent. */ + bool _animationTimescaleInd; + /** A rect that is used instead when an index is not found */ + cocos2d::Rect _undefinedIndexRect; /** does FlippedY variance of each particle */ int _yCoordFlipped; + /** Wether to use emission shapes for this particle system or not */ + bool _isEmissionShapes; + + /** variable keeping count of emission shapes added */ + int _emissionShapeIndex; + /** A map that stores emission shapes that are choosen at random */ + std::unordered_map _emissionShapes; + /** particles movement type: Free or Grouped @since v0.8 */ @@ -986,11 +1690,22 @@ protected: /** is the emitter paused */ bool _paused; + /** time scale of the particle system */ + float _timeScale; + + /** Fixed frame rate of the particle system */ + float _fixedFPS; + + /** Fixed frame rate delta (internal) */ + float _fixedFPSDelta; + /** is sourcePosition compatible */ bool _sourcePositionCompatible; static Vector __allInstances; + RngSeed _rng; + private: CC_DISALLOW_COPY_AND_ASSIGN(ParticleSystem); }; diff --git a/core/2d/CCParticleSystemQuad.cpp b/core/2d/CCParticleSystemQuad.cpp index cab5a697f2..9326319b8d 100644 --- a/core/2d/CCParticleSystemQuad.cpp +++ b/core/2d/CCParticleSystemQuad.cpp @@ -42,6 +42,7 @@ THE SOFTWARE. #include "base/ccUTF8.h" #include "renderer/ccShaders.h" #include "renderer/backend/ProgramState.h" +#include "2d/CCTweenFunction.h" NS_CC_BEGIN @@ -105,6 +106,10 @@ ParticleSystemQuad* ParticleSystemQuad::create(std::string_view filename) ParticleSystemQuad* ParticleSystemQuad::createWithTotalParticles(int numberOfParticles) { + CCASSERT(numberOfParticles <= 10000, + "Adding more than 10000 particles will crash the renderer, the mesh generated has an index format of " + "U_SHORT (uint16_t)"); + ParticleSystemQuad* ret = new ParticleSystemQuad(); if (ret->initWithTotalParticles(numberOfParticles)) { @@ -273,19 +278,24 @@ void ParticleSystemQuad::initIndices() } } -inline void updatePosWithParticle(V3F_C4B_T2F_Quad* quad, const Vec2& newPosition, float size, float rotation) +inline void updatePosWithParticle(V3F_C4B_T2F_Quad* quad, + const Vec2& newPosition, + float size, + float scaleInSize, + float rotation, + float staticRotation) { // vertices float size_2 = size / 2; - float x1 = -size_2; - float y1 = -size_2; + float x1 = -size_2 * scaleInSize; + float y1 = -size_2 * scaleInSize; - float x2 = size_2; - float y2 = size_2; + float x2 = size_2 * scaleInSize; + float y2 = size_2 * scaleInSize; float x = newPosition.x; float y = newPosition.y; - float r = (float)-CC_DEGREES_TO_RADIANS(rotation); + float r = (float)-CC_DEGREES_TO_RADIANS(rotation + staticRotation); float cr = cosf(r); float sr = sinf(r); float ax = x1 * cr - y1 * sr + x; @@ -357,16 +367,36 @@ void ParticleSystemQuad::updateParticleQuads() float* y = _particleData.posy; float* s = _particleData.size; float* r = _particleData.rotation; + float* sr = _particleData.staticRotation; + float* sid = _particleData.scaleInDelta; + float* sil = _particleData.scaleInLength; V3F_C4B_T2F_Quad* quadStart = startQuad; - for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r) + if (_isScaleInAllocated) { - p2.set(*startX, *startY, 0); - worldToNodeTM.transformPoint(&p2); - newPos.set(*x, *y); - p2 = p1 - p2; - newPos.x -= p2.x - pos.x; - newPos.y -= p2.y - pos.y; - updatePosWithParticle(quadStart, newPos, *s, *r); + for (int i = 0; i < _particleCount; + ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr, ++sid, ++sil) + { + p2.set(*startX, *startY, 0); + worldToNodeTM.transformPoint(&p2); + newPos.set(*x, *y); + p2 = p1 - p2; + newPos.x -= p2.x - pos.x; + newPos.y -= p2.y - pos.y; + updatePosWithParticle(quadStart, newPos, *s, tweenfunc::expoEaseOut(*sid / *sil), *r, *sr); + } + } + else + { + for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr) + { + p2.set(*startX, *startY, 0); + worldToNodeTM.transformPoint(&p2); + newPos.set(*x, *y); + p2 = p1 - p2; + newPos.x -= p2.x - pos.x; + newPos.y -= p2.y - pos.y; + updatePosWithParticle(quadStart, newPos, *s, 1.0F, *r, *sr); + } } } else if (_positionType == PositionType::RELATIVE) @@ -378,14 +408,32 @@ void ParticleSystemQuad::updateParticleQuads() float* y = _particleData.posy; float* s = _particleData.size; float* r = _particleData.rotation; + float* sr = _particleData.staticRotation; + float* sid = _particleData.scaleInDelta; + float* sil = _particleData.scaleInLength; V3F_C4B_T2F_Quad* quadStart = startQuad; - for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r) + if (_isScaleInAllocated) { - newPos.set(*x, *y); - newPos.x = *x - (currentPosition.x - *startX); - newPos.y = *y - (currentPosition.y - *startY); - newPos += pos; - updatePosWithParticle(quadStart, newPos, *s, *r); + for (int i = 0; i < _particleCount; + ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr, ++sid, ++sil) + { + newPos.set(*x, *y); + newPos.x = *x - (currentPosition.x - *startX); + newPos.y = *y - (currentPosition.y - *startY); + newPos += pos; + updatePosWithParticle(quadStart, newPos, *s, tweenfunc::expoEaseOut(*sid / *sil), *r, *sr); + } + } + else + { + for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr) + { + newPos.set(*x, *y); + newPos.x = *x - (currentPosition.x - *startX); + newPos.y = *y - (currentPosition.y - *startY); + newPos += pos; + updatePosWithParticle(quadStart, newPos, *s, 1.0F, *r, *sr); + } } } else @@ -397,53 +445,259 @@ void ParticleSystemQuad::updateParticleQuads() float* y = _particleData.posy; float* s = _particleData.size; float* r = _particleData.rotation; + float* sr = _particleData.staticRotation; + float* sid = _particleData.scaleInDelta; + float* sil = _particleData.scaleInLength; V3F_C4B_T2F_Quad* quadStart = startQuad; - for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r) + if (_isScaleInAllocated) { - newPos.set(*x + pos.x, *y + pos.y); - updatePosWithParticle(quadStart, newPos, *s, *r); + for (int i = 0; i < _particleCount; + ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr, ++sid, ++sil) + { + newPos.set(*x + pos.x, *y + pos.y); + updatePosWithParticle(quadStart, newPos, *s, tweenfunc::expoEaseOut(*sid / *sil), *r, *sr); + } + } + else + { + for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr) + { + newPos.set(*x + pos.x, *y + pos.y); + updatePosWithParticle(quadStart, newPos, *s, 1.0F, *r, *sr); + } } } - // set color - if (_opacityModifyRGB) - { - V3F_C4B_T2F_Quad* quad = startQuad; - float* r = _particleData.colorR; - float* g = _particleData.colorG; - float* b = _particleData.colorB; - float* a = _particleData.colorA; + V3F_C4B_T2F_Quad* quad = startQuad; + float* r = _particleData.colorR; + float* g = _particleData.colorG; + float* b = _particleData.colorB; + float* a = _particleData.colorA; - for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a) + if (_isOpacityFadeInAllocated) + { + float* fadeDt = _particleData.opacityFadeInDelta; + float* fadeLn = _particleData.opacityFadeInLength; + + // HSV calculation is expensive, so we should skip it if it's not enabled. + if (_isHSVAllocated) { - uint8_t colorR = *r * *a * 255; - uint8_t colorG = *g * *a * 255; - uint8_t colorB = *b * *a * 255; - uint8_t colorA = *a * 255; - quad->bl.colors.set(colorR, colorG, colorB, colorA); - quad->br.colors.set(colorR, colorG, colorB, colorA); - quad->tl.colors.set(colorR, colorG, colorB, colorA); - quad->tr.colors.set(colorR, colorG, colorB, colorA); + float* hue = _particleData.hue; + float* sat = _particleData.sat; + float* val = _particleData.val; + + if (_opacityModifyRGB) + { + auto hsv = HSV(); + for (int i = 0; i < _particleCount; + ++i, ++quad, ++r, ++g, ++b, ++a, ++hue, ++sat, ++val, ++fadeDt, ++fadeLn) + { + float colorR = *r; + float colorG = *g; + float colorB = *b; + float colorA = *a * (*fadeDt / *fadeLn); + hsv.set(colorR, colorG, colorB, colorA); + hsv.h += *hue; + hsv.s = abs(*sat); + hsv.v = abs(*val); + auto colF = hsv.toColor4F(); + quad->bl.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + quad->br.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + quad->tl.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + quad->tr.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + } + } + else + { + auto hsv = HSV(); + for (int i = 0; i < _particleCount; + ++i, ++quad, ++r, ++g, ++b, ++a, ++hue, ++sat, ++val, ++fadeDt, ++fadeLn) + { + float colorR = *r; + float colorG = *g; + float colorB = *b; + float colorA = *a * (*fadeDt / *fadeLn); + hsv.set(colorR, colorG, colorB, colorA); + hsv.h += *hue; + hsv.s = abs(*sat); + hsv.v = abs(*val); + auto col = hsv.toColor4B(); + quad->bl.colors.set(col.r, col.g, col.b, col.a); + quad->br.colors.set(col.r, col.g, col.b, col.a); + quad->tl.colors.set(col.r, col.g, col.b, col.a); + quad->tr.colors.set(col.r, col.g, col.b, col.a); + } + } + } + else + { + // set color + if (_opacityModifyRGB) + { + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a, ++fadeDt, ++fadeLn) + { + uint8_t colorR = *r * *a * 255; + uint8_t colorG = *g * *a * 255; + uint8_t colorB = *b * *a * 255; + uint8_t colorA = *a * (*fadeDt / *fadeLn) * 255; + quad->bl.colors.set(colorR, colorG, colorB, colorA); + quad->br.colors.set(colorR, colorG, colorB, colorA); + quad->tl.colors.set(colorR, colorG, colorB, colorA); + quad->tr.colors.set(colorR, colorG, colorB, colorA); + } + } + else + { + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a, ++fadeDt, ++fadeLn) + { + uint8_t colorR = *r * 255; + uint8_t colorG = *g * 255; + uint8_t colorB = *b * 255; + uint8_t colorA = *a * (*fadeDt / *fadeLn) * 255; + quad->bl.colors.set(colorR, colorG, colorB, colorA); + quad->br.colors.set(colorR, colorG, colorB, colorA); + quad->tl.colors.set(colorR, colorG, colorB, colorA); + quad->tr.colors.set(colorR, colorG, colorB, colorA); + } + } } } else { - V3F_C4B_T2F_Quad* quad = startQuad; - float* r = _particleData.colorR; - float* g = _particleData.colorG; - float* b = _particleData.colorB; - float* a = _particleData.colorA; - - for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a) + // HSV calculation is expensive, so we should skip it if it's not enabled. + if (_isHSVAllocated) { - uint8_t colorR = *r * 255; - uint8_t colorG = *g * 255; - uint8_t colorB = *b * 255; - uint8_t colorA = *a * 255; - quad->bl.colors.set(colorR, colorG, colorB, colorA); - quad->br.colors.set(colorR, colorG, colorB, colorA); - quad->tl.colors.set(colorR, colorG, colorB, colorA); - quad->tr.colors.set(colorR, colorG, colorB, colorA); + float* hue = _particleData.hue; + float* sat = _particleData.sat; + float* val = _particleData.val; + + if (_opacityModifyRGB) + { + auto hsv = HSV(); + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a, ++hue, ++sat, ++val) + { + float colorR = *r; + float colorG = *g; + float colorB = *b; + float colorA = *a; + hsv.set(colorR, colorG, colorB, colorA); + hsv.h += *hue; + hsv.s = abs(*sat); + hsv.v = abs(*val); + auto colF = hsv.toColor4F(); + quad->bl.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + quad->br.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + quad->tl.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + quad->tr.colors.set(colF.r * colF.a * 255.0F, colF.g * colF.a * 255.0F, colF.b * colF.a * 255.0F, + colF.a * 255.0F); + } + } + else + { + auto hsv = HSV(); + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a, ++hue, ++sat, ++val) + { + float colorR = *r; + float colorG = *g; + float colorB = *b; + float colorA = *a; + hsv.set(colorR, colorG, colorB, colorA); + hsv.h += *hue; + hsv.s = abs(*sat); + hsv.v = abs(*val); + auto col = hsv.toColor4B(); + quad->bl.colors.set(col.r, col.g, col.b, col.a); + quad->br.colors.set(col.r, col.g, col.b, col.a); + quad->tl.colors.set(col.r, col.g, col.b, col.a); + quad->tr.colors.set(col.r, col.g, col.b, col.a); + } + } + } + else + { + // set color + if (_opacityModifyRGB) + { + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a) + { + uint8_t colorR = *r * *a * 255; + uint8_t colorG = *g * *a * 255; + uint8_t colorB = *b * *a * 255; + uint8_t colorA = *a * 255; + quad->bl.colors.set(colorR, colorG, colorB, colorA); + quad->br.colors.set(colorR, colorG, colorB, colorA); + quad->tl.colors.set(colorR, colorG, colorB, colorA); + quad->tr.colors.set(colorR, colorG, colorB, colorA); + } + } + else + { + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a) + { + uint8_t colorR = *r * 255; + uint8_t colorG = *g * 255; + uint8_t colorB = *b * 255; + uint8_t colorA = *a * 255; + quad->bl.colors.set(colorR, colorG, colorB, colorA); + quad->br.colors.set(colorR, colorG, colorB, colorA); + quad->tl.colors.set(colorR, colorG, colorB, colorA); + quad->tr.colors.set(colorR, colorG, colorB, colorA); + } + } + } + } + + // The reason for using for-loops separately for every property is because + // When the processor needs to read from or write to a location in memory, + // it first checks whether a copy of that data is in the cpu's cache. + // And wether if every property's memory of the particle system is continuous, + // for the purpose of improving cache hit rate, we should process only one property in one for-loop. + // It was proved to be effective especially for low-end devices. + if (_isLifeAnimated || _isEmitterAnimated || _isLoopAnimated || _isAnimAllocated) + { + V3F_C4B_T2F_Quad* quad = startQuad; + unsigned short* cellIndex = _particleData.animCellIndex; + + ParticleFrameDescriptor index; + for (int i = 0; i < _particleCount; ++i, ++quad, ++cellIndex) + { + float left = 0.0F, bottom = 0.0F, top = 1.0F, right = 1.0F; + + // TODO: index.isRotated should be treated accordingly + + auto iter = _animationIndices.find(*cellIndex); + if (iter == _animationIndices.end()) + index.rect = {0, 0, float(_texture->getPixelsWide()), float(_texture->getPixelsHigh())}; + else + index = iter->second; + + auto texWidth = _texture->getPixelsWide(); + auto texHeight = _texture->getPixelsHigh(); + + left = index.rect.origin.x / texWidth; + right = (index.rect.origin.x + index.rect.size.x) / texWidth; + + top = index.rect.origin.y / texHeight; + bottom = (index.rect.origin.y + index.rect.size.y) / texHeight; + + quad->bl.texCoords.u = left; + quad->bl.texCoords.v = bottom; + + quad->br.texCoords.u = right; + quad->br.texCoords.v = bottom; + + quad->tl.texCoords.u = left; + quad->tl.texCoords.v = top; + + quad->tr.texCoords.u = right; + quad->tr.texCoords.v = top; } } } diff --git a/core/base/ccUtils.cpp b/core/base/ccUtils.cpp index 9d43f9ee6b..8c64385aa1 100644 --- a/core/base/ccUtils.cpp +++ b/core/base/ccUtils.cpp @@ -780,6 +780,15 @@ std::string urlDecode(std::string_view st) return decoded; } +CC_DLL uint32_t fourccValue(std::string_view str) +{ + if (str.empty() || str[0] != '#') + return (uint32_t)-1; + uint32_t value = 0; + memcpy(&value, str.data() + 1, std::min(sizeof(value), str.size() - 1)); + return value; +} + } // namespace utils NS_CC_END diff --git a/core/base/ccUtils.h b/core/base/ccUtils.h index 70a0a1498e..29546d40a4 100644 --- a/core/base/ccUtils.h +++ b/core/base/ccUtils.h @@ -404,6 +404,8 @@ inline char* char2hex(char* p, unsigned char c, unsigned char a = 'a') CC_DLL std::string urlEncode(std::string_view s); CC_DLL std::string urlDecode(std::string_view st); + +CC_DLL uint32_t fourccValue(std::string_view str); } // namespace utils NS_CC_END diff --git a/core/math/RngSeed.hpp b/core/math/RngSeed.hpp new file mode 100644 index 0000000000..d983bf1bfc --- /dev/null +++ b/core/math/RngSeed.hpp @@ -0,0 +1,88 @@ +#ifndef RNGSEED_H_ +#define RNGSEED_H_ + +/** A more effective seeded random number generator struct, made by kiss rng. + * @since adxe-1.0.0b8 + */ +struct RngSeed +{ + const unsigned long RNG_RAND_MAX = 4294967295; + const unsigned long RNG_RAND_MAX_SIGNED = 2147483647; + unsigned long _x = 1; + unsigned long _y = 2; + unsigned long _z = 4; + unsigned long _w = 8; + unsigned long _carry = 0; + unsigned long _k = 0; + unsigned long _m = 0; + unsigned long _seed = 0; + + RngSeed() { seed_rand(time(NULL)); } + + // initialize this object with seed + void seed_rand(unsigned long seed) + { + _seed = seed; + _x = seed | 1; + _y = seed | 2; + _z = seed | 4; + _w = seed | 8; + _carry = 0; + } + + // returns an unsigned long random value + unsigned long rand() + { + _x = _x * 69069 + 1; + _y ^= _y << 13; + _y ^= _y >> 17; + _y ^= _y << 5; + _k = (_z >> 2) + (_w >> 3) + (_carry >> 2); + _m = _w + _w + _z + _carry; + _z = _w; + _w = _m; + _carry = _k >> 30; + return _x + _y + _w; + } + + // returns a random integer from min to max + int range(int min, int max) + { + return floor(min + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (max - min)))); + } + + // returns a random unsigned integer from min to max + unsigned int rangeu(unsigned int min, unsigned int max) + { + return floor(min + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (max - min)))); + } + + // returns a random float from min to max + float rangef(float min = -1.0F, float max = 1.0F) + { + return min + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (max - min))); + } + + // returns a random integer from 0 to max + int max(int max = INT_MAX) + { + return floor(0 + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (max - 0)))); + } + + // returns a random unsigned integer from 0 to max + unsigned int maxu(unsigned int max = UINT_MAX) + { + return floor(0 + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (max - 0)))); + } + + // returns a random float from 0.0 to max + float maxf(float max) { return 0 + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (max - 0))); } + + // returns a random float from 0.0 to 1.0 + float float01() { return 0 + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (1 - 0))); } + + // returns either false or true randomly + bool bool01() { return (bool)floor(0 + static_cast(rand()) / (static_cast(RNG_RAND_MAX / (2 - 0)))); } +}; + +#endif diff --git a/tests/cpp-tests/Classes/ParticleTest/ParticleTest.cpp b/tests/cpp-tests/Classes/ParticleTest/ParticleTest.cpp index 425c830dc7..ee20f23fb7 100644 --- a/tests/cpp-tests/Classes/ParticleTest/ParticleTest.cpp +++ b/tests/cpp-tests/Classes/ParticleTest/ParticleTest.cpp @@ -678,6 +678,1123 @@ std::string ParallaxParticle::subtitle() const return "Parallax + Particles"; } +//------------------------------------------------------------------ +// +// DemoFixedFPS +// +//------------------------------------------------------------------ +void DemoFixedFPS::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setFixedFPS(15.0F); + + setEmitterPosition(); +} + +std::string DemoFixedFPS::subtitle() const +{ + return "Particle Fixed FPS set to 15"; +} + +//------------------------------------------------------------------ +// +// DemoTimeScale +// +//------------------------------------------------------------------ +void DemoTimeScale::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setTag(2); + + setEmitterPosition(); +} + +std::string DemoTimeScale::subtitle() const +{ + return "Particle system timescale should alternate between 1.0 and 0.0"; +} + +void DemoTimeScale::update(float dt) +{ + auto p = (ParticleSystem*)getChildByTag(2); + + elapsedTime += Director::getInstance()->getDeltaTime(); + auto scale = (sin(elapsedTime * 4) + 1.0F) / 2.0F; + p->setTimeScale(scale); +} + +//------------------------------------------------------------------ +// +// DemoSpawnFadeIn +// +//------------------------------------------------------------------ +void DemoSpawnFadeIn::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setSpawnFadeIn(0.5F); + _emitter->setSpawnFadeInVar(0); + + setEmitterPosition(); +} + +std::string DemoSpawnFadeIn::subtitle() const +{ + return "Particle spawn fade in set to 0.5 seconds"; +} + +//------------------------------------------------------------------ +// +// DemoScaleFadeIn +// +//------------------------------------------------------------------ +void DemoScaleFadeIn::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setSpawnScaleIn(1); + _emitter->setSpawnScaleInVar(0); + + setEmitterPosition(); +} + +std::string DemoScaleFadeIn::subtitle() const +{ + return "Particle spawn scale in set to 1.0 seconds"; +} + +//------------------------------------------------------------------ +// +// DemoSimulation +// +//------------------------------------------------------------------ +void DemoSimulation::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->simulate(3.0F); + + setEmitterPosition(); +} + +std::string DemoSimulation::subtitle() const +{ + return "Particle simulation, particle system should advance 3 seconds in at the start"; +} + +//------------------------------------------------------------------ +// +// DemoSpawnRotation +// +//------------------------------------------------------------------ +void DemoSpawnRotation::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setSpawnAngle(0); + _emitter->setSpawnAngleVar(180); + + setEmitterPosition(); +} + +std::string DemoSpawnRotation::subtitle() const +{ + return "Particle rotation, particles should spawn with a random rotation"; +} + +//------------------------------------------------------------------ +// +// DemoHSV +// +//------------------------------------------------------------------ +void DemoHSV::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->useHSV(true); + _emitter->setHSV(HSV(90, 0.5F, 0.5F)); + _emitter->setHSVVar(HSV(90, 0.5F, 0.5F)); + + setEmitterPosition(); +} + +std::string DemoHSV::subtitle() const +{ + return "Particle HSV color system"; +} + +//------------------------------------------------------------------ +// +// DemoLifeAnimation +// +//------------------------------------------------------------------ +void DemoLifeAnimation::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setLifeAnimation(true); + + _emitter->resetAnimationIndices(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + setEmitterPosition(); +} + +std::string DemoLifeAnimation::subtitle() const +{ + return "Particle life animation, particles should show 0 to 9 based on their life"; +} + +//------------------------------------------------------------------ +// +// DemoLifeAnimationAtlas +// +//------------------------------------------------------------------ +void DemoLifeAnimationAtlas::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setLifeAnimation(true); + + _emitter->resetAnimationIndices(); + + _emitter->setAnimationIndicesAtlas(); + + setEmitterPosition(); +} + +std::string DemoLifeAnimationAtlas::subtitle() const +{ + return "Particle life animation, particles should show 0 to 9 based on their life based on a texture atlas NOT using sprite frames"; +} + +//------------------------------------------------------------------ +// +// DemoLifeAnimation +// +//------------------------------------------------------------------ +void DemoLifeAnimationReversed::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setLifeAnimation(true); + + _emitter->resetAnimationIndices(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + _emitter->setAnimationReverse(true); + + setEmitterPosition(); +} + +std::string DemoLifeAnimationReversed::subtitle() const +{ + return "Particle life animation, particles should show 9 to 0 reversed based on their life"; +} + +//------------------------------------------------------------------ +// +// DemoLoopAnimation +// +//------------------------------------------------------------------ +void DemoLoopAnimation::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setLoopAnimation(true); + + _emitter->resetAnimationIndices(); + _emitter->resetAnimationDescriptors(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + _emitter->setAnimationDescriptor(0, 1, 0.5F, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + setEmitterPosition(); +} + +std::string DemoLoopAnimation::subtitle() const +{ + return "Particle loop animation, particles should loop from 0 to 9 in one second with 0.5 variance"; +} + +//------------------------------------------------------------------ +// +// DemoLoopAnimationReversed +// +//------------------------------------------------------------------ +void DemoLoopAnimationReversed::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setLoopAnimation(true); + + _emitter->resetAnimationIndices(); + _emitter->resetAnimationDescriptors(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + _emitter->setAnimationDescriptor(0, 1, 0.5F, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, true /* Reversed animation descriptor */); + + setEmitterPosition(); +} + +std::string DemoLoopAnimationReversed::subtitle() const +{ + return "Particle loop animation, particles should loop from 9 to 0 reversed in one second with 0.5 variance"; +} + +//------------------------------------------------------------------ +// +// DemoEmitterAnimation +// +//------------------------------------------------------------------ +void DemoEmitterAnimation::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setEmitterAnimation(true); + + _emitter->resetAnimationIndices(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + setEmitterPosition(); +} + +std::string DemoEmitterAnimation::subtitle() const +{ + return "Particle emitter animation, particles should spawn with a fixed random index"; +} + +//------------------------------------------------------------------ +// +// DemoEmitterAnimation +// +//------------------------------------------------------------------ +void DemoEmitterAnimationDescriptor::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setEmitterAnimation(true); + + _emitter->resetAnimationIndices(); + _emitter->resetAnimationDescriptors(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + _emitter->setAnimationDescriptor(0, 1, 0.5F, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + setEmitterPosition(); +} + +std::string DemoEmitterAnimationDescriptor::subtitle() const +{ + return "Particle emitter animation, particles should have their index changed every one second with a 0.5 variance to a random index"; +} + +//------------------------------------------------------------------ +// +// DemoLoopAnimationMultiDescriptor +// +//------------------------------------------------------------------ +void DemoLoopAnimationMultiDescriptor::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Particles/animation_1.plist"); + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage("Particles/animation_1.png")); + + _emitter->setBlendAdditive(true); + + _emitter->setStartColor({1, 1, 1, 1}); + _emitter->setStartColorVar({0, 0, 0, 0}); + _emitter->setEndColor({1, 1, 1, 1}); + _emitter->setEndColorVar({0, 0, 0, 0}); + + _emitter->setEmissionRate(10); + + _emitter->setStartSize(16); + _emitter->setEndSize(16); + + _emitter->setLoopAnimation(true); + + _emitter->resetAnimationIndices(); + _emitter->resetAnimationDescriptors(); + + _emitter->addAnimationIndex("particle_anim_0.png"); + _emitter->addAnimationIndex("particle_anim_1.png"); + _emitter->addAnimationIndex("particle_anim_2.png"); + _emitter->addAnimationIndex("particle_anim_3.png"); + _emitter->addAnimationIndex("particle_anim_4.png"); + _emitter->addAnimationIndex("particle_anim_5.png"); + _emitter->addAnimationIndex("particle_anim_6.png"); + _emitter->addAnimationIndex("particle_anim_7.png"); + _emitter->addAnimationIndex("particle_anim_8.png"); + _emitter->addAnimationIndex("particle_anim_9.png"); + + _emitter->setAnimationDescriptor(0, 1, 0.5F, {1, 3, 8}); + _emitter->setAnimationDescriptor(1, 1, 0.5F, {0, 1}); + _emitter->setAnimationDescriptor(2, 1, 0.5F, {0, 9}); + + setEmitterPosition(); +} + +std::string DemoLoopAnimationMultiDescriptor::subtitle() const +{ + return "Particle loop animation, particles should play a random animation descriptor"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapePoint +// +//------------------------------------------------------------------ +void DemoEmissionShapePoint::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createPointShape({40, 40})); + _emitter->addEmissionShape(ParticleSystem::createPointShape({-40, 40})); + _emitter->addEmissionShape(ParticleSystem::createPointShape({40, -40})); + _emitter->addEmissionShape(ParticleSystem::createPointShape({-40, -40})); + + setEmitterPosition(); +} + +std::string DemoEmissionShapePoint::subtitle() const +{ + return "Particle emission shape point"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeRect +// +//------------------------------------------------------------------ +void DemoEmissionShapeRect::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createRectShape({0, 0}, {300, 100})); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeRect::subtitle() const +{ + return "Particle emission shape rectangle"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeRectTorus +// +//------------------------------------------------------------------ +void DemoEmissionShapeRectTorus::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createRectTorusShape({0, 0}, {100, 100}, {150, 150})); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeRectTorus::subtitle() const +{ + return "Particle emission shape rectangular torus"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeCircle +// +//------------------------------------------------------------------ +void DemoEmissionShapeCircle::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createCircleShape({0, 0}, 150, 2.0F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeCircle::subtitle() const +{ + return "Particle emission shape circle"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeCircleBias +// +//------------------------------------------------------------------ +void DemoEmissionShapeCircleBiasEdge::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createCircleShape({0, 0}, 150, 6.0F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeCircleBiasEdge::subtitle() const +{ + return "Particle emission shape circle with bias towards the edge"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeCircleBiasCenter +// +//------------------------------------------------------------------ +void DemoEmissionShapeCircleBiasCenter::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createCircleShape({0, 0}, 150, 0.75F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeCircleBiasCenter::subtitle() const +{ + return "Particle emission shape circle with bias towards the center"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeTorus +// +//------------------------------------------------------------------ +void DemoEmissionShapeTorus::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createTorusShape({0, 0}, 80, 150, 2.0F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeTorus::subtitle() const +{ + return "Particle emission shape torus"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeTorusBiasEdge +// +//------------------------------------------------------------------ +void DemoEmissionShapeTorusBiasEdge::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createTorusShape({0, 0}, 80, 150, 6.0F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeTorusBiasEdge::subtitle() const +{ + return "Particle emission shape torus with bias towards the edge"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeTorusBiasInner +// +//------------------------------------------------------------------ +void DemoEmissionShapeTorusBiasInner::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createTorusShape({0, 0}, 80, 150, 0.5F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeTorusBiasInner::subtitle() const +{ + return "Particle emission shape torus with bias towards the inner radius"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeCone +// +//------------------------------------------------------------------ +void DemoEmissionShapeCone::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createConeShape({0, 0}, 150, 0, 90, 2.0F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeCone::subtitle() const +{ + return "Particle emission shape cone"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeCone +// +//------------------------------------------------------------------ +void DemoEmissionShapeConeTorus::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::create(); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(10); + + _emitter->setEmissionShapes(true); + + _emitter->addEmissionShape(ParticleSystem::createConeTorusShape({0, 0}, 100, 150, 0, 120, 2.0F /* Bias */)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeConeTorus::subtitle() const +{ + return "Particle emission shape cone torus"; +} + +//------------------------------------------------------------------ +// +// DemoEmissionShapeAlphaMask +// +//------------------------------------------------------------------ +void DemoEmissionShapeAlphaMask::onEnter() +{ + ParticleDemo::onEnter(); + + _color->setColor(Color3B::BLACK); + removeChild(_background, true); + _background = nullptr; + + _emitter = ParticleFireworks::createWithTotalParticles(8000); + _emitter->retain(); + addChild(_emitter, 10); + _emitter->setTexture(Director::getInstance()->getTextureCache()->addImage(s_stars1)); + + _emitter->setBlendAdditive(true); + + _emitter->setGravity({0, 0}); + _emitter->setSpeed(0); + _emitter->setSpeedVar(0); + + _emitter->setStartSize(3); + _emitter->setEndSize(3); + + _emitter->setEmissionRate(1000); + + _emitter->setEmissionShapes(true); + + auto cache = ParticleEmissionMaskCache::getInstance(); + cache->bakeEmissionMask("#msk1"sv, "Particles/mask.png", 0.5F, false, 1); + + _emitter->addEmissionShape(ParticleSystem::createMaskShape("#msk1"sv, {0, 0}, {400, 200}, Vec2::ONE)); + + setEmitterPosition(); +} + +std::string DemoEmissionShapeAlphaMask::subtitle() const +{ + return "Particle emission shape alpha mask texture"; +} + //------------------------------------------------------------------ // // RadiusMode1 @@ -1051,6 +2168,37 @@ ParticleTests::ParticleTests() addTestCase("ButterFlyYFlipped", []() { return DemoParticleFromFile::create("ButterFlyYFlipped"); }); ADD_TEST_CASE(RadiusMode1); ADD_TEST_CASE(RadiusMode2); + + ADD_TEST_CASE(DemoFixedFPS); + ADD_TEST_CASE(DemoTimeScale); + ADD_TEST_CASE(DemoSimulation); + ADD_TEST_CASE(DemoSpawnFadeIn); + ADD_TEST_CASE(DemoScaleFadeIn); + ADD_TEST_CASE(DemoSpawnRotation); + ADD_TEST_CASE(DemoHSV); + + ADD_TEST_CASE(DemoLifeAnimation); + ADD_TEST_CASE(DemoLifeAnimationAtlas); + ADD_TEST_CASE(DemoLifeAnimationReversed); + ADD_TEST_CASE(DemoLoopAnimation); + ADD_TEST_CASE(DemoLoopAnimationReversed); + ADD_TEST_CASE(DemoLoopAnimationMultiDescriptor); + ADD_TEST_CASE(DemoEmitterAnimation); + ADD_TEST_CASE(DemoEmitterAnimationDescriptor); + + ADD_TEST_CASE(DemoEmissionShapePoint); + ADD_TEST_CASE(DemoEmissionShapeRect); + ADD_TEST_CASE(DemoEmissionShapeRectTorus); + ADD_TEST_CASE(DemoEmissionShapeCircle); + ADD_TEST_CASE(DemoEmissionShapeCircleBiasEdge); + ADD_TEST_CASE(DemoEmissionShapeCircleBiasCenter); + ADD_TEST_CASE(DemoEmissionShapeTorus); + ADD_TEST_CASE(DemoEmissionShapeTorusBiasEdge); + ADD_TEST_CASE(DemoEmissionShapeTorusBiasInner); + ADD_TEST_CASE(DemoEmissionShapeCone); + ADD_TEST_CASE(DemoEmissionShapeConeTorus); + ADD_TEST_CASE(DemoEmissionShapeAlphaMask); + ADD_TEST_CASE(Issue704); ADD_TEST_CASE(Issue870); ADD_TEST_CASE(Issue1201); diff --git a/tests/cpp-tests/Classes/ParticleTest/ParticleTest.h b/tests/cpp-tests/Classes/ParticleTest/ParticleTest.h index 42ec6f9ee0..3d8b780b53 100644 --- a/tests/cpp-tests/Classes/ParticleTest/ParticleTest.h +++ b/tests/cpp-tests/Classes/ParticleTest/ParticleTest.h @@ -182,6 +182,225 @@ public: virtual std::string subtitle() const override; }; +class DemoFixedFPS : public ParticleDemo +{ +public: + CREATE_FUNC(DemoFixedFPS); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoTimeScale: public ParticleDemo +{ +public: + CREATE_FUNC(DemoTimeScale); + virtual void onEnter() override; + virtual std::string subtitle() const override; + virtual void update(float dt) override; + + float elapsedTime = 0.0F; +}; + +class DemoSpawnFadeIn : public ParticleDemo +{ +public: + CREATE_FUNC(DemoSpawnFadeIn); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoScaleFadeIn : public ParticleDemo +{ +public: + CREATE_FUNC(DemoScaleFadeIn); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoSimulation : public ParticleDemo +{ +public: + CREATE_FUNC(DemoSimulation); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoSpawnRotation : public ParticleDemo +{ +public: + CREATE_FUNC(DemoSpawnRotation); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoHSV : public ParticleDemo +{ +public: + CREATE_FUNC(DemoHSV); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoLifeAnimation : public ParticleDemo +{ +public: + CREATE_FUNC(DemoLifeAnimation); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoLifeAnimationAtlas : public ParticleDemo +{ +public: + CREATE_FUNC(DemoLifeAnimationAtlas); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoLifeAnimationReversed : public ParticleDemo +{ +public: + CREATE_FUNC(DemoLifeAnimationReversed); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoLoopAnimation : public ParticleDemo +{ +public: + CREATE_FUNC(DemoLoopAnimation); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoLoopAnimationReversed : public ParticleDemo +{ +public: + CREATE_FUNC(DemoLoopAnimationReversed); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmitterAnimation : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmitterAnimation); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmitterAnimationDescriptor : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmitterAnimationDescriptor); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoLoopAnimationMultiDescriptor : public ParticleDemo +{ +public: + CREATE_FUNC(DemoLoopAnimationMultiDescriptor); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapePoint : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapePoint); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeRect : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeRect); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeRectTorus : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeRectTorus); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeCircle : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeCircle); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeCircleBiasEdge : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeCircleBiasEdge); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeCircleBiasCenter : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeCircleBiasCenter); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeTorus : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeTorus); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeTorusBiasEdge : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeTorusBiasEdge); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeTorusBiasInner : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeTorusBiasInner); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeCone : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeCone); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeConeTorus : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeConeTorus); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + +class DemoEmissionShapeAlphaMask : public ParticleDemo +{ +public: + CREATE_FUNC(DemoEmissionShapeAlphaMask); + virtual void onEnter() override; + virtual std::string subtitle() const override; +}; + class DemoParticleFromFile : public ParticleDemo { public: diff --git a/tests/cpp-tests/Resources/Particles/animation_1.plist b/tests/cpp-tests/Resources/Particles/animation_1.plist new file mode 100644 index 0000000000..4e951331a7 --- /dev/null +++ b/tests/cpp-tests/Resources/Particles/animation_1.plist @@ -0,0 +1,176 @@ + + + + + frames + + particle_anim_0.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{0,0},{64,64}} + textureRotated + + + particle_anim_1.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{64,0},{64,64}} + textureRotated + + + particle_anim_2.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{128,0},{64,64}} + textureRotated + + + particle_anim_3.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{192,0},{64,64}} + textureRotated + + + particle_anim_4.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{256,0},{64,64}} + textureRotated + + + particle_anim_5.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{320,0},{64,64}} + textureRotated + + + particle_anim_6.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{384,0},{64,64}} + textureRotated + + + particle_anim_7.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{448,0},{64,64}} + textureRotated + + + particle_anim_8.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{512,0},{64,64}} + textureRotated + + + particle_anim_9.png + + aliases + + spriteOffset + {0,0} + spriteSize + {64,64} + spriteSourceSize + {64,64} + textureRect + {{576,0},{64,64}} + textureRotated + + + + metadata + + format + 3 + pixelFormat + RGBA8888 + premultiplyAlpha + + realTextureFileName + animation_1.png + size + {640,64} + smartupdate + $TexturePacker:SmartUpdate:da26e187ca02fabf90d1710d32ee7cf9:36c91cc4cb0ce67ab1706c682518e467:5e97592c8b58bf2d3aec7c12fc605c76$ + textureFileName + animation_1.png + + + diff --git a/tests/cpp-tests/Resources/Particles/animation_1.png b/tests/cpp-tests/Resources/Particles/animation_1.png new file mode 100644 index 0000000000..8e057a23bd Binary files /dev/null and b/tests/cpp-tests/Resources/Particles/animation_1.png differ diff --git a/tests/cpp-tests/Resources/Particles/mask.png b/tests/cpp-tests/Resources/Particles/mask.png new file mode 100644 index 0000000000..292b6279fe Binary files /dev/null and b/tests/cpp-tests/Resources/Particles/mask.png differ