diff --git a/core/2d/CCParticleSystem.cpp b/core/2d/CCParticleSystem.cpp index daaf7e9928..c8a8435c74 100644 --- a/core/2d/CCParticleSystem.cpp +++ b/core/2d/CCParticleSystem.cpp @@ -98,18 +98,23 @@ inline void normalize_point(float x, float y, particle_point* out) } /** - A more effect random number getter function, get from ejoy2d. + A more effective random number generator function that fixes strafing for position variance, made by kiss rng. + KEEP IT SIMPLE STUPID (KISS) rng example: https://gist.github.com/3ki5tj/7b1d51e96d1f9bfb89bc */ -inline static float RANDOM_M11(unsigned int* seed) +inline static float RANDOM_KISS(void) { - *seed = *seed * 134775813 + 1; - union - { - uint32_t d; - float f; - } u; - u.d = (((uint32_t)(*seed) & 0x7fff) << 8) | 0x40000000; - return u.f - 3.0f; +#define kiss_znew(z) (z = 36969 * (z & 65535) + (z >> 16)) +#define kiss_wnew(w) (w = 18000 * (w & 65535) + (w >> 16)) +#define kiss_MWC(z, w) ((kiss_znew(z) << 16) + kiss_wnew(w)) +#define kiss_SHR3(jsr) (jsr ^= (jsr << 17), jsr ^= (jsr >> 13), jsr ^= (jsr << 5)) +#define kiss_CONG(jc) (jc = 69069 * jc + 1234567) +#define kiss_KISS(z, w, jc, jsr) ((kiss_MWC(z, w) ^ kiss_CONG(jc)) + kiss_SHR3(jsr)) + + static unsigned kiss_z = rand(), kiss_w = rand(), kiss_jsr = rand(), kiss_jcong = rand(); + // Generate two random floats and add them to get a total of 2.0 and then subtract 1.0 + // to get a random number between -1.0 and 1.0 INCLUSIVE. + return -1.0F + ((kiss_KISS(kiss_z, kiss_w, kiss_jcong, kiss_jsr) / 4294967296.0) + + (kiss_KISS(kiss_z, kiss_w, kiss_jcong, kiss_jsr) / 4294967296.0)); } ParticleData::ParticleData() @@ -121,24 +126,33 @@ 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)); + hue = (float*)malloc(count * sizeof(float)); + sat = (float*)malloc(count * sizeof(float)); + val = (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)); + animTimeLength = (float*)malloc(count * sizeof(float)); + animTimeDelta = (float*)malloc(count * sizeof(float)); + animIndex = (unsigned short*)malloc(count * sizeof(unsigned short)); + animCellIndex = (unsigned short*)malloc(count * sizeof(unsigned short)); + atlasIndex = (unsigned int*)malloc(count * sizeof(unsigned int)); modeA.dirX = (float*)malloc(count * sizeof(float)); modeA.dirY = (float*)malloc(count * sizeof(float)); @@ -150,10 +164,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 && hue && sat && val && size && deltaSize && rotation && + staticRotation && deltaRotation && totalTimeToLive && timeToLive && animTimeLength && animTimeDelta && + animIndex && animCellIndex && atlasIndex && modeA.dirX && modeA.dirY && modeA.radialAccel && + modeA.tangentialAccel && modeB.angle && modeB.degreesPerSecond && modeB.deltaRadius && modeB.radius; } void ParticleData::release() @@ -170,11 +185,20 @@ 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(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); @@ -218,14 +242,29 @@ ParticleSystem::ParticleSystem() , _startSpinVar(0) , _endSpin(0) , _endSpinVar(0) + , _spawnAngle(0) + , _spawnAngleVar(0) + , _hsv(0, 1, 1) + , _hsvVar(0, 0, 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) , _positionType(PositionType::FREE) , _paused(false) + , _updatePaused(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(); @@ -604,14 +643,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 animationCellIndex, int animationIndex) { if (_paused) return; - uint32_t RANDSEED = rand(); + + // Try to add as many particles as you can 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 +664,70 @@ 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 * RANDOM_KISS(); + _particleData.totalTimeToLive[i] = MAX(0, particleLife); + _particleData.timeToLive[i] = MAX(0, particleLife); } // position for (int i = start; i < _particleCount; ++i) { - _particleData.posx[i] = _sourcePosition.x + _posVar.x * RANDOM_M11(&RANDSEED); + auto f = RANDOM_KISS(); + _particleData.posx[i] = _sourcePosition.x + _posVar.x * RANDOM_KISS(); } for (int i = start; i < _particleCount; ++i) { - _particleData.posy[i] = _sourcePosition.y + _posVar.y * RANDOM_M11(&RANDSEED); + _particleData.posy[i] = _sourcePosition.y + _posVar.y * RANDOM_KISS(); + } + + if (animationCellIndex == -1 && _isEmitterAnimated) + { + for (int i = start; i < _particleCount; ++i) + { + int rand0 = abs(RANDOM_KISS() * _animIndexCount); + _particleData.animCellIndex[i] = MIN(rand0, _animIndexCount - 1); + } + } + + if (animationCellIndex != -1) + std::fill_n(_particleData.animCellIndex + start, _particleCount - start, animationCellIndex); + + if (animationIndex == -1 && !_animations.empty()) + { + if (_randomAnimations.empty()) + setMultiAnimationRandom(); + + for (int i = start; i < _particleCount; ++i) + { + int rand0 = abs(RANDOM_KISS() * _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 * RANDOM_KISS(); + } + } + + if (_isEmitterAnimated || _isLoopAnimated) + std::fill_n(_particleData.animTimeDelta + start, _particleCount - start, 0); + + 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 * RANDOM_KISS(); + } } // color #define SET_COLOR(c, b, v) \ for (int i = start; i < _particleCount; ++i) \ { \ - c[i] = clampf(b + v * RANDOM_M11(&RANDSEED), 0, 1); \ + c[i] = clampf(b + v * RANDOM_KISS(), 0, 1); \ } SET_COLOR(_particleData.colorR, _startColor.r, _startColorVar.r); @@ -662,10 +751,28 @@ void ParticleSystem::addParticles(int count) SET_DELTA_COLOR(_particleData.colorB, _particleData.deltaColorB); SET_DELTA_COLOR(_particleData.colorA, _particleData.deltaColorA); + // hue saturation value color + { + for (int i = start; i < _particleCount; ++i) + { + _particleData.hue[i] = _hsv.h + _hsvVar.h * RANDOM_KISS(); + } + + for (int i = start; i < _particleCount; ++i) + { + _particleData.sat[i] = _hsv.s + _hsvVar.s * RANDOM_KISS(); + } + + for (int i = start; i < _particleCount; ++i) + { + _particleData.val[i] = _hsv.v + _hsvVar.v * RANDOM_KISS(); + } + } + // size for (int i = start; i < _particleCount; ++i) { - _particleData.size[i] = _startSize + _startSizeVar * RANDOM_M11(&RANDSEED); + _particleData.size[i] = _startSize + _startSizeVar * RANDOM_KISS(); _particleData.size[i] = MAX(0, _particleData.size[i]); } @@ -673,30 +780,31 @@ void ParticleSystem::addParticles(int count) { for (int i = start; i < _particleCount; ++i) { - float endSize = _endSize + _endSizeVar * RANDOM_M11(&RANDSEED); + float endSize = _endSize + _endSizeVar * RANDOM_KISS(); 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 * RANDOM_KISS(); } for (int i = start; i < _particleCount; ++i) { - float endA = _endSpin + _endSpinVar * RANDOM_M11(&RANDSEED); + float endA = _endSpin + _endSpinVar * RANDOM_KISS(); _particleData.deltaRotation[i] = (endA - _particleData.rotation[i]) / _particleData.timeToLive[i]; } + // static rotation + for (int i = start; i < _particleCount; ++i) + { + _particleData.staticRotation[i] = _spawnAngle + _spawnAngleVar * RANDOM_KISS(); + } + // position Vec2 pos; if (_positionType == PositionType::FREE) @@ -707,14 +815,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 +825,14 @@ 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 * RANDOM_KISS(); } // tangential accel for (int i = start; i < _particleCount; ++i) { _particleData.modeA.tangentialAccel[i] = - modeA.tangentialAccel + modeA.tangentialAccelVar * RANDOM_M11(&RANDSEED); + modeA.tangentialAccel + modeA.tangentialAccelVar * RANDOM_KISS(); } // rotation is dir @@ -738,9 +840,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 * RANDOM_KISS()); Vec2 v(cosf(a), sinf(a)); - float s = modeA.speed + modeA.speedVar * RANDOM_M11(&RANDSEED); + float s = modeA.speed + modeA.speedVar * RANDOM_KISS(); Vec2 dir = v * s; _particleData.modeA.dirX[i] = dir.x; // v * s ; _particleData.modeA.dirY[i] = dir.y; @@ -751,9 +853,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 * RANDOM_KISS()); Vec2 v(cosf(a), sinf(a)); - float s = modeA.speed + modeA.speedVar * RANDOM_M11(&RANDSEED); + float s = modeA.speed + modeA.speedVar * RANDOM_KISS(); Vec2 dir = v * s; _particleData.modeA.dirX[i] = dir.x; // v * s ; _particleData.modeA.dirY[i] = dir.y; @@ -768,32 +870,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 * RANDOM_KISS(); } 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 * RANDOM_KISS()); } 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 * RANDOM_KISS()); } 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 * RANDOM_KISS(); _particleData.modeB.deltaRadius[i] = (endRadius - _particleData.modeB.radius[i]) / _particleData.timeToLive[i]; } @@ -801,6 +898,181 @@ 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::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) + return addAnimationIndex(index, frame->getRect(), 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; + + auto& desc = iter->second; + desc.rect = rect; + desc.isRotated = rotated; + + ++_animIndexCount; + + return true; +} + +void ParticleSystem::simulate(float seconds, float frameRate) +{ + auto l_updatePaused = _updatePaused; + _updatePaused = false; + 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); + _updatePaused = l_updatePaused; +} + +void ParticleSystem::resimulate(float seconds, float frameRate) +{ + this->resetSystem(); + this->simulate(seconds, frameRate); +} + void ParticleSystem::onEnter() { Node::onEnter(); @@ -834,10 +1106,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 +1117,29 @@ 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 || _updatePaused) + 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) + return; + dt = _fixedFPSDelta; + _fixedFPSDelta = 0.0F; + } + + float pureDt = dt; + dt *= _timeScale; + if (_isActive && _emissionRate) { float rate = 1.0f / _emissionRate; @@ -859,8 +1149,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,10 +1165,65 @@ 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 (_isEmitterAnimated && !_animations.empty()) + { + _particleData.animTimeDelta[i] += (_animationTimescaleInd ? pureDt : dt); + if (_particleData.animTimeDelta[i] > _particleData.animTimeLength[i]) + { + auto& anim = _animations.at(_particleData.animIndex[i]); + float percent = abs(RANDOM_KISS()); + 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()) + { + 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()) + { + 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()) + { + 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) @@ -952,12 +1296,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 +1353,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(); @@ -1386,4 +1724,39 @@ void ParticleSystem::resumeEmissions() _paused = false; } +bool ParticleSystem::isUpdatePaused() const +{ + return _updatePaused; +} + +void ParticleSystem::pauseUpdate() +{ + _updatePaused = true; +} + +void ParticleSystem::resumeUpdate() +{ + _updatePaused = false; +} + +float ParticleSystem::getFixedFPS() +{ + return _fixedFPS; +} + +void ParticleSystem::setFixedFPS(float frameRate) +{ + _fixedFPS = frameRate; +} + +float ParticleSystem::getTimeScale() +{ + return _timeScale; +} + +void ParticleSystem::setTimeScale(float scale) +{ + _timeScale = scale; +} + NS_CC_END diff --git a/core/2d/CCParticleSystem.h b/core/2d/CCParticleSystem.h index 4cd110e10f..040db16a75 100644 --- a/core/2d/CCParticleSystem.h +++ b/core/2d/CCParticleSystem.h @@ -32,6 +32,8 @@ THE SOFTWARE. #include "base/CCProtocols.h" #include "2d/CCNode.h" #include "base/CCValue.h" +#include "2d/CCSpriteFrame.h" +#include "2d/CCSpriteFrameCache.h" NS_CC_BEGIN @@ -52,6 +54,26 @@ struct particle_point float y; }; +/** @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 +92,21 @@ public: float* deltaColorB; float* deltaColorA; + float* hue; + float* sat; + float* val; + 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,15 +150,24 @@ public: deltaColorB[p1] = deltaColorB[p2]; deltaColorA[p1] = deltaColorA[p2]; - size[p1] = size[p2]; - deltaSize[p1] = deltaSize[p2]; + hue[p1] = hue[p2]; + sat[p1] = sat[p2]; + val[p1] = val[p2]; - rotation[p1] = rotation[p2]; - deltaRotation[p1] = deltaRotation[p2]; + size[p1] = size[p2]; + deltaSize[p1] = deltaSize[p2]; + rotation[p1] = rotation[p2]; + staticRotation[p1] = staticRotation[p2]; + deltaRotation[p1] = deltaRotation[p2]; - timeToLive[p1] = timeToLive[p2]; + totalTimeToLive[p1] = totalTimeToLive[p2]; + timeToLive[p1] = timeToLive[p2]; + animTimeDelta[p1] = animTimeDelta[p2]; + animTimeLength[p1] = animTimeLength[p2]; - atlasIndex[p1] = atlasIndex[p2]; + animIndex[p1] = animIndex[p2]; + animCellIndex[p1] = animCellIndex[p2]; + atlasIndex[p1] = atlasIndex[p2]; modeA.dirX[p1] = modeA.dirX[p2]; modeA.dirY[p1] = modeA.dirY[p2]; @@ -202,7 +243,7 @@ public: }; /** PositionType - Possible types of particle positions. + Types of particle positioning. * @js cc.ParticleSystem.TYPE_FREE */ enum class PositionType @@ -216,6 +257,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 +279,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. @@ -252,7 +310,7 @@ public: static Vector& getAllParticleSystems(); public: - void addParticles(int count); + void addParticles(int count, int animationCellIndex = -1, int animationIndex = -1); void stopSystem(); /** Kill all living particles. @@ -658,6 +716,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) { _isHsv = hsv; }; + bool isHSV() { return _isHsv; }; + + /** 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 +812,28 @@ 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 emission rate of the particles. * * @return The emission rate of the particles. @@ -728,6 +860,160 @@ 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) + { + _isLifeAnimated = enabled; + _isEmitterAnimated = false; + _isLoopAnimated = false; + } + + /** Enables or disables tex coord animations that are set by the emitter randomly when a particle is emitted. */ + void setEmitterAnimation(bool enabled) + { + _isEmitterAnimated = enabled; + _isLifeAnimated = false; + _isLoopAnimated = false; + } + + /** Enables or disables tex coord animations that are used to make particles play a sequence forever until they die */ + void setLoopAnimation(bool enabled) + { + _isLoopAnimated = enabled; + _isEmitterAnimated = false; + _isLifeAnimated = false; + } + + 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; }; + /** Gets the particles movement type: Free or Grouped. @since v0.8 * @@ -741,6 +1027,23 @@ 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 screens refresh rate. + * + * @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 +1115,43 @@ public: */ virtual bool isPaused() const; - /* Pause the emissions*/ + /* Pause the emissions */ virtual void pauseEmissions(); - /* UnPause the emissions*/ + /* Unpause the emissions */ virtual void resumeEmissions(); + /** Is system update paused + @return True if the emissions are paused, else false + */ + virtual bool isUpdatePaused() const; + + /* Pause the particles from being updated */ + virtual void pauseUpdate(); + + /* Unpause the particles from being updated */ + virtual void resumeUpdate(); + + /** 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. + @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(); + + /** Gets 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 +1291,12 @@ protected: Color4F _endColor; /** end color variance of each particle */ Color4F _endColorVar; + //* Is the hsv system used or not. + bool _isHsv; + /** 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 +1305,10 @@ protected: float _endSpin; //* initial angle of each particle float _endSpinVar; + //* initial rotation of each particle + float _spawnAngle; + //* initial rotation of each particle + float _spawnAngleVar; /** emission rate of the particles */ float _emissionRate; /** maximum particles of the system */ @@ -975,6 +1319,26 @@ 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; @@ -986,6 +1350,18 @@ protected: /** is the emitter paused */ bool _paused; + /** is particle system update paused */ + bool _updatePaused; + + /** 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; diff --git a/core/2d/CCParticleSystemQuad.cpp b/core/2d/CCParticleSystemQuad.cpp index cab5a697f2..365fe0c847 100644 --- a/core/2d/CCParticleSystemQuad.cpp +++ b/core/2d/CCParticleSystemQuad.cpp @@ -105,6 +105,8 @@ 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,7 +275,11 @@ 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 rotation, + float staticRotation) { // vertices float size_2 = size / 2; @@ -285,7 +291,7 @@ inline void updatePosWithParticle(V3F_C4B_T2F_Quad* quad, const Vec2& newPositio 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; @@ -351,14 +357,15 @@ void ParticleSystemQuad::updateParticleQuads() worldToNodeTM.transformPoint(&p1); Vec3 p2; Vec2 newPos; - float* startX = _particleData.startPosX; - float* startY = _particleData.startPosY; - float* x = _particleData.posx; - float* y = _particleData.posy; - float* s = _particleData.size; - float* r = _particleData.rotation; + float* startX = _particleData.startPosX; + float* startY = _particleData.startPosY; + float* x = _particleData.posx; + float* y = _particleData.posy; + float* s = _particleData.size; + float* r = _particleData.rotation; + float* sr = _particleData.staticRotation; V3F_C4B_T2F_Quad* quadStart = startQuad; - for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r) + for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r, ++sr) { p2.set(*startX, *startY, 0); worldToNodeTM.transformPoint(&p2); @@ -366,7 +373,7 @@ void ParticleSystemQuad::updateParticleQuads() p2 = p1 - p2; newPos.x -= p2.x - pos.x; newPos.y -= p2.y - pos.y; - updatePosWithParticle(quadStart, newPos, *s, *r); + updatePosWithParticle(quadStart, newPos, *s, *r, *sr); } } else if (_positionType == PositionType::RELATIVE) @@ -378,14 +385,15 @@ void ParticleSystemQuad::updateParticleQuads() float* y = _particleData.posy; float* s = _particleData.size; float* r = _particleData.rotation; + float* sr = _particleData.staticRotation; V3F_C4B_T2F_Quad* quadStart = startQuad; - for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r) + 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, *r); + updatePosWithParticle(quadStart, newPos, *s, *r, *sr); } } else @@ -397,53 +405,146 @@ void ParticleSystemQuad::updateParticleQuads() float* y = _particleData.posy; float* s = _particleData.size; float* r = _particleData.rotation; + float* sr = _particleData.staticRotation; V3F_C4B_T2F_Quad* quadStart = startQuad; - for (int i = 0; i < _particleCount; ++i, ++startX, ++startY, ++x, ++y, ++quadStart, ++s, ++r) + 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, *r); + updatePosWithParticle(quadStart, newPos, *s, *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) + // HSV calculation is expensive, so we should skip it if it's not enabled. + if (_isHsv) + { + float* hue = _particleData.hue; + float* sat = _particleData.sat; + float* val = _particleData.val; + + if (_opacityModifyRGB) { - 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); + auto hsv = HSV(); + for (int i = 0; i < _particleCount; ++i, ++quad, ++r, ++g, ++b, ++a, ++hue, ++sat, ++val) + { + float colorR = *r * *a; + float colorG = *g * *a; + float colorB = *b * *a; + 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 + { + 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 { - 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) + // set color + if (_opacityModifyRGB) { - 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); + 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) + { + 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 = _undefinedIndexRect; + 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/tests/cpp-tests/Classes/SpriteTest/SpriteTest.cpp b/tests/cpp-tests/Classes/SpriteTest/SpriteTest.cpp index 59b5263083..6d2a306222 100644 --- a/tests/cpp-tests/Classes/SpriteTest/SpriteTest.cpp +++ b/tests/cpp-tests/Classes/SpriteTest/SpriteTest.cpp @@ -86,7 +86,6 @@ SpriteTests::SpriteTests() ADD_TEST_CASE(SpriteChildrenAnchorPoint); ADD_TEST_CASE(SpriteBatchNodeChildrenAnchorPoint); ADD_TEST_CASE(SpriteColorOpacity); - ADD_TEST_CASE(SpriteColorOpacityHSVHSL); ADD_TEST_CASE(SpriteBatchNodeColorOpacity); ADD_TEST_CASE(SpriteZOrder); ADD_TEST_CASE(SpriteBatchNodeZOrder); @@ -362,118 +361,6 @@ std::string SpriteColorOpacity::subtitle() const return "Color & Opacity"; } -//------------------------------------------------------------------ -// -// SpriteColorOpacityHSVHSL -// -//------------------------------------------------------------------ - -SpriteColorOpacityHSVHSL::SpriteColorOpacityHSVHSL() -{ - auto sprite1 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 0, 121 * 1, 85, 121)); - auto sprite2 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 1, 121 * 1, 85, 121)); - auto sprite3 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 2, 121 * 1, 85, 121)); - auto sprite4 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 3, 121 * 1, 85, 121)); - - auto sprite5 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 0, 121 * 1, 85, 121)); - auto sprite6 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 1, 121 * 1, 85, 121)); - auto sprite7 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 2, 121 * 1, 85, 121)); - auto sprite8 = Sprite::create("Images/grossini_dance_atlas.png", Rect(85 * 3, 121 * 1, 85, 121)); - - auto s = Director::getInstance()->getWinSize(); - sprite1->setPosition(Vec2((s.width / 5) * 1, (s.height / 3) * 1)); - sprite2->setPosition(Vec2((s.width / 5) * 2, (s.height / 3) * 1)); - sprite3->setPosition(Vec2((s.width / 5) * 3, (s.height / 3) * 1)); - sprite4->setPosition(Vec2((s.width / 5) * 4, (s.height / 3) * 1)); - sprite5->setPosition(Vec2((s.width / 5) * 1, (s.height / 3) * 2)); - sprite6->setPosition(Vec2((s.width / 5) * 2, (s.height / 3) * 2)); - sprite7->setPosition(Vec2((s.width / 5) * 3, (s.height / 3) * 2)); - sprite8->setPosition(Vec2((s.width / 5) * 4, (s.height / 3) * 2)); - - auto action = FadeIn::create(2); - auto action_back = action->reverse(); - auto fade = RepeatForever::create(Sequence::create(action, action_back, nullptr)); - - auto col = HSV(0, 1, 1, 1).toColor3B(); - auto tintred = TintBy::create(2, col.r, col.g, col.b); - auto tintred_back = tintred->reverse(); - auto red = RepeatForever::create(Sequence::create(tintred, tintred_back, nullptr)); - - col = HSV(120, 1, 1, 1).toColor3B(); - auto tintgreen = TintBy::create(2, col.r, col.g, col.b); - auto tintgreen_back = tintgreen->reverse(); - auto green = RepeatForever::create(Sequence::create(tintgreen, tintgreen_back, nullptr)); - - col = HSV(240, 1, 1, 1).toColor3B(); - auto tintblue = TintBy::create(2, col.r, col.g, col.b); - auto tintblue_back = tintblue->reverse(); - auto blue = RepeatForever::create(Sequence::create(tintblue, tintblue_back, nullptr)); - - sprite1->runAction(red); - sprite2->runAction(green); - sprite3->runAction(blue); - sprite4->runAction(fade); - - action = FadeIn::create(2); - action_back = action->reverse(); - fade = RepeatForever::create(Sequence::create(action, action_back, nullptr)); - - col = HSL(0, 1, .7, 1).toColor3B(); - tintred = TintBy::create(2, col.r, col.g, col.b); - tintred_back = tintred->reverse(); - red = RepeatForever::create(Sequence::create(tintred, tintred_back, nullptr)); - - col = HSL(120, 1, .7, 1).toColor3B(); - tintgreen = TintBy::create(2, col.r, col.g, col.b); - tintgreen_back = tintgreen->reverse(); - green = RepeatForever::create(Sequence::create(tintgreen, tintgreen_back, nullptr)); - - col = HSL(240, 1, .7, 1).toColor3B(); - tintblue = TintBy::create(2, col.r, col.g, col.b); - tintblue_back = tintblue->reverse(); - blue = RepeatForever::create(Sequence::create(tintblue, tintblue_back, nullptr)); - - sprite5->runAction(red); - sprite6->runAction(green); - sprite7->runAction(blue); - sprite8->runAction(fade); - - // late add: test dirtyColor and dirtyPosition - addChild(sprite1, 0, kTagSprite1); - addChild(sprite2, 0, kTagSprite2); - addChild(sprite3, 0, kTagSprite3); - addChild(sprite4, 0, kTagSprite4); - addChild(sprite5, 0, kTagSprite5); - addChild(sprite6, 0, kTagSprite6); - addChild(sprite7, 0, kTagSprite7); - addChild(sprite8, 0, kTagSprite8); - - schedule(CC_CALLBACK_1(SpriteColorOpacityHSVHSL::removeAndAddSprite, this), 2, "remove_add_key"); -} - -// this function test if remove and add works as expected: -// color array and vertex array should be reindexed -void SpriteColorOpacityHSVHSL::removeAndAddSprite(float dt) -{ - auto sprite = static_cast(getChildByTag(kTagSprite5)); - sprite->retain(); - - removeChild(sprite, false); - addChild(sprite, 0, kTagSprite5); - - sprite->release(); -} - -std::string SpriteColorOpacityHSVHSL::title() const -{ - return "Testing Sprite"; -} - -std::string SpriteColorOpacityHSVHSL::subtitle() const -{ - return "Color & Opacity using HSV/HSL"; -} - //------------------------------------------------------------------ // // SpriteBatchNodeColorOpacity diff --git a/tests/cpp-tests/Classes/SpriteTest/SpriteTest.h b/tests/cpp-tests/Classes/SpriteTest/SpriteTest.h index e4755c3f3a..daea4588da 100644 --- a/tests/cpp-tests/Classes/SpriteTest/SpriteTest.h +++ b/tests/cpp-tests/Classes/SpriteTest/SpriteTest.h @@ -73,16 +73,6 @@ public: virtual std::string subtitle() const override; }; -class SpriteColorOpacityHSVHSL : public SpriteTestDemo -{ -public: - CREATE_FUNC(SpriteColorOpacityHSVHSL); - SpriteColorOpacityHSVHSL(); - void removeAndAddSprite(float dt); - virtual std::string title() const override; - virtual std::string subtitle() const override; -}; - class SpriteBatchNodeColorOpacity : public SpriteTestDemo { public: