From 0a1fe16b337e54fbc53c10288fd59307ac5a38ea Mon Sep 17 00:00:00 2001 From: DelinWorks Date: Wed, 15 Jun 2022 23:04:08 +0300 Subject: [PATCH] Add cpp_tests and improve system. --- core/2d/CCParticleSystem.cpp | 150 +-- core/2d/CCParticleSystem.h | 114 +- .../Classes/ParticleTest/ParticleTest.cpp | 1148 +++++++++++++++++ .../Classes/ParticleTest/ParticleTest.h | 219 ++++ .../Resources/Particles/animation_1.plist | 176 +++ .../Resources/Particles/animation_1.png | Bin 0 -> 8121 bytes tests/cpp-tests/Resources/Particles/mask.png | Bin 0 -> 17534 bytes 7 files changed, 1688 insertions(+), 119 deletions(-) create mode 100644 tests/cpp-tests/Resources/Particles/animation_1.plist create mode 100644 tests/cpp-tests/Resources/Particles/animation_1.png create mode 100644 tests/cpp-tests/Resources/Particles/mask.png diff --git a/core/2d/CCParticleSystem.cpp b/core/2d/CCParticleSystem.cpp index 9c9b27251b..992af5538e 100644 --- a/core/2d/CCParticleSystem.cpp +++ b/core/2d/CCParticleSystem.cpp @@ -97,36 +97,6 @@ inline void normalize_point(float x, float y, particle_point* out) out->y = y * n; } -/** - 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 - - Generates a random number between 0.0 to 1.0 INCLUSIVE. - */ -inline static float RANDOM_KISS_ABS(void) -{ -#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(); - return kiss_KISS(kiss_z, kiss_w, kiss_jcong, kiss_jsr) / 4294967296.0; -} - -/** - 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 - - Generates a random number between -1.0 and 1.0 INCLUSIVE. - */ -inline static float RANDOM_KISS(void) -{ - return -1.0F + RANDOM_KISS_ABS() + RANDOM_KISS_ABS(); -} - ParticleData::ParticleData() { memset(this, 0, sizeof(ParticleData)); @@ -279,7 +249,6 @@ ParticleSystem::ParticleSystem() , _emissionShapeIndex(0) , _positionType(PositionType::FREE) , _paused(false) - , _updatePaused(false) , _timeScale(1) , _fixedFPS(0) , _fixedFPSDelta(0) @@ -777,7 +746,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe // life for (int i = start; i < _particleCount; ++i) { - float particleLife = _life + _lifeVar * RANDOM_KISS(); + float particleLife = _life + _lifeVar * _rng.rangef(); _particleData.totalTimeToLive[i] = MAX(0, particleLife); _particleData.timeToLive[i] = MAX(0, particleLife); } @@ -788,12 +757,12 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { if (_emissionShapes.empty()) { - _particleData.posx[i] = _sourcePosition.x + _posVar.x * RANDOM_KISS(); - _particleData.posy[i] = _sourcePosition.y + _posVar.y * RANDOM_KISS(); + _particleData.posx[i] = _sourcePosition.x + _posVar.x * _rng.rangef(); + _particleData.posy[i] = _sourcePosition.y + _posVar.y * _rng.rangef(); continue; } - auto randElem = RANDOM_KISS_ABS(); + auto randElem = _rng.float01(); auto& shape = _emissionShapes[MIN(randElem * _emissionShapes.size(), _emissionShapes.size() - 1)]; switch (shape.type) @@ -807,29 +776,29 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe } case EmissionShapeType::RECT: { - _particleData.posx[i] = _sourcePosition.x + shape.x + shape.innerWidth / 2 * RANDOM_KISS(); - _particleData.posy[i] = _sourcePosition.y + shape.y + shape.innerHeight / 2 * RANDOM_KISS(); + _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) * RANDOM_KISS_ABS() + shape.innerWidth; - float height = (shape.outerHeight - shape.innerHeight) * RANDOM_KISS_ABS() + shape.innerHeight; - width = RANDOM_KISS() < 0.0F ? width * -1 : width; - height = RANDOM_KISS() < 0.0F ? height * -1 : height; - float prob = RANDOM_KISS(); - _particleData.posx[i] = _sourcePosition.x + shape.x + width / 2 * (prob >= 0.0F ? 1.0F : RANDOM_KISS()); - _particleData.posy[i] = _sourcePosition.y + shape.y + height / 2 * (prob < 0.0F ? 1.0F : RANDOM_KISS()); + 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 = RANDOM_KISS_ABS() * shape.innerRadius / shape.innerRadius; + auto val = _rng.float01() * shape.innerRadius / shape.innerRadius; val = powf(val, 1 / shape.edgeElasticity); auto point = Vec2(0.0F, val * shape.innerRadius); - point = point.rotateByAngle(Vec2::ZERO, -CC_DEGREES_TO_RADIANS(shape.coneOffset + shape.coneAngle / 2 * RANDOM_KISS())); + 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; @@ -837,10 +806,10 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe } case EmissionShapeType::TORUS: { - auto val = RANDOM_KISS_ABS() * shape.outerRadius / shape.outerRadius; + auto val = _rng.float01() * shape.outerRadius / shape.outerRadius; val = powf(val, 1 / shape.edgeElasticity); 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 * RANDOM_KISS())); + 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; @@ -861,7 +830,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe Vec2 point = {0, 0}; - int rand0 = RANDOM_KISS_ABS() * mask.points.size(); + int rand0 = _rng.float01() * mask.points.size(); int index = MIN(rand0, mask.points.size() - 1); point = mask.points[index]; @@ -885,12 +854,12 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe // position for (int i = start; i < _particleCount; ++i) { - _particleData.posx[i] = _sourcePosition.x + _posVar.x * RANDOM_KISS(); + _particleData.posx[i] = _sourcePosition.x + _posVar.x * _rng.rangef(); } for (int i = start; i < _particleCount; ++i) { - _particleData.posy[i] = _sourcePosition.y + _posVar.y * RANDOM_KISS(); + _particleData.posy[i] = _sourcePosition.y + _posVar.y * _rng.rangef(); } } @@ -911,7 +880,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe _particleData.animIndex[i] = animationIndex; auto& descriptor = _animations.at(animationIndex); _particleData.animTimeLength[i] = - descriptor.animationSpeed + descriptor.animationSpeedVariance * RANDOM_KISS(); + descriptor.animationSpeed + descriptor.animationSpeedVariance * _rng.rangef(); } } } @@ -922,7 +891,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - int rand0 = RANDOM_KISS_ABS() * _animIndexCount; + int rand0 = _rng.float01() * _animIndexCount; _particleData.animCellIndex[i] = MIN(rand0, _animIndexCount - 1); } } @@ -934,12 +903,12 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe for (int i = start; i < _particleCount; ++i) { - int rand0 = RANDOM_KISS_ABS() * _randomAnimations.size(); + 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 * RANDOM_KISS(); + descriptor.animationSpeed + descriptor.animationSpeedVariance * _rng.rangef(); } } @@ -951,7 +920,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe #define SET_COLOR(c, b, v) \ for (int i = start; i < _particleCount; ++i) \ { \ - c[i] = clampf(b + v * RANDOM_KISS(), 0, 1); \ + c[i] = clampf(b + v * _rng.rangef(), 0, 1); \ } SET_COLOR(_particleData.colorR, _startColor.r, _startColorVar.r); @@ -980,7 +949,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - _particleData.opacityFadeInLength[i] = _spawnFadeIn + _spawnFadeInVar * RANDOM_KISS(); + _particleData.opacityFadeInLength[i] = _spawnFadeIn + _spawnFadeInVar * _rng.rangef(); } std::fill_n(_particleData.opacityFadeInDelta + start, _particleCount - start, 0.0F); } @@ -990,7 +959,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - _particleData.scaleInLength[i] = _spawnScaleIn + _spawnScaleInVar * RANDOM_KISS(); + _particleData.scaleInLength[i] = _spawnScaleIn + _spawnScaleInVar * _rng.rangef(); } std::fill_n(_particleData.scaleInDelta + start, _particleCount - start, 0.0F); } @@ -1000,24 +969,24 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - _particleData.hue[i] = _hsv.h + _hsvVar.h * RANDOM_KISS(); + _particleData.hue[i] = _hsv.h + _hsvVar.h * _rng.rangef(); } for (int i = start; i < _particleCount; ++i) { - _particleData.sat[i] = _hsv.s + _hsvVar.s * RANDOM_KISS(); + _particleData.sat[i] = _hsv.s + _hsvVar.s * _rng.rangef(); } for (int i = start; i < _particleCount; ++i) { - _particleData.val[i] = _hsv.v + _hsvVar.v * RANDOM_KISS(); + _particleData.val[i] = _hsv.v + _hsvVar.v * _rng.rangef(); } } // size for (int i = start; i < _particleCount; ++i) { - _particleData.size[i] = _startSize + _startSizeVar * RANDOM_KISS(); + _particleData.size[i] = _startSize + _startSizeVar * _rng.rangef(); _particleData.size[i] = MAX(0, _particleData.size[i]); } @@ -1025,7 +994,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - float endSize = _endSize + _endSizeVar * RANDOM_KISS(); + float endSize = _endSize + _endSizeVar * _rng.rangef(); endSize = MAX(0, endSize); _particleData.deltaSize[i] = (endSize - _particleData.size[i]) / _particleData.timeToLive[i]; } @@ -1036,18 +1005,18 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe // rotation for (int i = start; i < _particleCount; ++i) { - _particleData.rotation[i] = _startSpin + _startSpinVar * RANDOM_KISS(); + _particleData.rotation[i] = _startSpin + _startSpinVar * _rng.rangef(); } for (int i = start; i < _particleCount; ++i) { - float endA = _endSpin + _endSpinVar * RANDOM_KISS(); + 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 * RANDOM_KISS(); + _particleData.staticRotation[i] = _spawnAngle + _spawnAngleVar * _rng.rangef(); } // position @@ -1070,13 +1039,13 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe // radial accel for (int i = start; i < _particleCount; ++i) { - _particleData.modeA.radialAccel[i] = modeA.radialAccel + modeA.radialAccelVar * RANDOM_KISS(); + _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_KISS(); + _particleData.modeA.tangentialAccel[i] = modeA.tangentialAccel + modeA.tangentialAccelVar * _rng.rangef(); } // rotation is dir @@ -1084,9 +1053,9 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * RANDOM_KISS()); + float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * _rng.rangef()); Vec2 v(cosf(a), sinf(a)); - float s = modeA.speed + modeA.speedVar * RANDOM_KISS(); + 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; @@ -1097,9 +1066,9 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * RANDOM_KISS()); + float a = CC_DEGREES_TO_RADIANS(_angle + _angleVar * _rng.rangef()); Vec2 v(cosf(a), sinf(a)); - float s = modeA.speed + modeA.speedVar * RANDOM_KISS(); + 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; @@ -1114,18 +1083,18 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe // 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_KISS(); + _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_KISS()); + _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_KISS()); + CC_DEGREES_TO_RADIANS(modeB.rotatePerSecond + modeB.rotatePerSecondVar * _rng.rangef()); } if (modeB.endRadius == START_RADIUS_EQUAL_TO_END_RADIUS) @@ -1134,7 +1103,7 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe { for (int i = start; i < _particleCount; ++i) { - float endRadius = modeB.endRadius + modeB.endRadiusVar * RANDOM_KISS(); + float endRadius = modeB.endRadius + modeB.endRadiusVar * _rng.rangef(); _particleData.modeB.deltaRadius[i] = (endRadius - _particleData.modeB.radius[i]) / _particleData.timeToLive[i]; } @@ -1483,7 +1452,12 @@ bool ParticleSystem::addAnimationIndex(cocos2d::SpriteFrame* frame) bool ParticleSystem::addAnimationIndex(unsigned short index, cocos2d::SpriteFrame* frame) { if (frame) - return addAnimationIndex(index, frame->getRect(), frame->isRotated()); + { + auto rect = frame->getRectInPixels(); + rect.size.x = frame->getOriginalSizeInPixels().x; + rect.size.y = frame->getOriginalSizeInPixels().y; + return addAnimationIndex(index, rect, frame->isRotated()); + } return false; } @@ -1505,8 +1479,6 @@ bool ParticleSystem::addAnimationIndex(unsigned short index, cocos2d::Rect rect, 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() @@ -1523,7 +1495,6 @@ void ParticleSystem::simulate(float seconds, float frameRate) } else this->update(seconds); - _updatePaused = l_updatePaused; } void ParticleSystem::resimulate(float seconds, float frameRate) @@ -1577,7 +1548,7 @@ bool ParticleSystem::isFull() void ParticleSystem::update(float dt) { // don't process particles nor update gl buffer when this node is invisible. - if (!_visible || _updatePaused) + if (!_visible) return; CC_PROFILER_START_CATEGORY(kProfilerCategoryParticles, "CCParticleSystem - update"); @@ -1668,7 +1639,7 @@ void ParticleSystem::update(float dt) if (_particleData.animTimeDelta[i] > _particleData.animTimeLength[i]) { auto& anim = _animations.at(_particleData.animIndex[i]); - float percent = RANDOM_KISS_ABS(); + float percent = _rng.float01(); percent = anim.reverseIndices ? 1.0F - percent : percent; _particleData.animCellIndex[i] = anim.animationIndices[MIN( @@ -2264,21 +2235,6 @@ 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; diff --git a/core/2d/CCParticleSystem.h b/core/2d/CCParticleSystem.h index db8a2f6b2b..65c9018563 100644 --- a/core/2d/CCParticleSystem.h +++ b/core/2d/CCParticleSystem.h @@ -54,10 +54,92 @@ struct particle_point float y; }; +//* A more effective random number generator function that fixes strafing for position variance, made by kiss rng. +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; + unsigned long _m; + unsigned long _seed; + + 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)))); } +}; + /** * Particle emission shapes. * Current supported shapes are Point, Rectangle, RectangularTorus, Circle, Torus - * @since adxe-1.0.0b7 + * @since adxe-1.0.0b8 */ enum class EmissionShapeType { @@ -71,7 +153,7 @@ enum class EmissionShapeType /** * Particle emission mask descriptor. - * @since adxe-1.0.0b7 + * @since adxe-1.0.0b8 */ struct ParticleEmissionMaskDescriptor { @@ -82,7 +164,7 @@ struct ParticleEmissionMaskDescriptor /** * Particle emission shapes. * Current supported shapes are Point, Rectangle, RectangularTorus, Circle, Torus, Cone, Cone Torus - * @since adxe-1.0.0b7 + * @since adxe-1.0.0b8 */ struct EmissionShape { @@ -259,7 +341,7 @@ public: /** * Particle emission mask cache. - * @since adxe-1.0.0b7 + * @since adxe-1.0.0b8 */ class CC_DLL ParticleEmissionMaskCache : public cocos2d::Ref { @@ -1021,14 +1103,14 @@ public: */ void setSpawnFadeInVar(float time); - /** Gets the spawn opacity fade in time of each particle. + /** 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 opacity fade in time of each particle when it's created. + /** 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. * @@ -1036,14 +1118,14 @@ public: */ void setSpawnScaleIn(float time); - /** Gets the spawn opacity fade in time variance of each particle. + /** 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 opacity fade in time variance of each particle when it's created. + /** 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. * @@ -1448,17 +1530,6 @@ public: /* Unpause the emissions */ virtual void resumeEmissions(); - /** Is system update paused - @return True if system update is 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. */ @@ -1694,9 +1765,6 @@ protected: /** is the emitter paused */ bool _paused; - /** is particle system update paused */ - bool _updatePaused; - /** time scale of the particle system */ float _timeScale; @@ -1711,6 +1779,8 @@ protected: static Vector __allInstances; + RngSeed _rng; + private: CC_DISALLOW_COPY_AND_ASSIGN(ParticleSystem); }; diff --git a/tests/cpp-tests/Classes/ParticleTest/ParticleTest.cpp b/tests/cpp-tests/Classes/ParticleTest/ParticleTest.cpp index 425c830dc7..39c1749c7f 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("mask_001"sv, "Particles/mask.png", 0.5F, false, 1); + + _emitter->addEmissionShape(ParticleSystem::createMaskShape("mask_001"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 0000000000000000000000000000000000000000..8e057a23bdd0fded46f648ff24add43110a8e63a GIT binary patch literal 8121 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 L+YnNzX&?1J9cT7k literal 0 HcmV?d00001 diff --git a/tests/cpp-tests/Resources/Particles/mask.png b/tests/cpp-tests/Resources/Particles/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..292b6279fe4e8f35a899aee0293d85cb965d5d33 GIT binary patch literal 17534 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