diff --git a/core/2d/CCParticleSystem.cpp b/core/2d/CCParticleSystem.cpp index 43fcc4c20e..15ae448c11 100644 --- a/core/2d/CCParticleSystem.cpp +++ b/core/2d/CCParticleSystem.cpp @@ -836,6 +836,30 @@ void ParticleSystem::addParticles(int count, int animationIndex, int animationCe break; } + case EmissionShapeType::ALPHA_MASK: + { + Vec2 pos = {shape.x, shape.y}; + Vec2 size = shape.mask.size; + Vec2 overrideSize = {shape.innerWidth, shape.innerHeight}; + Vec2 scale = {shape.outerWidth, shape.outerHeight}; + float angle = shape.coneOffset; + + if (overrideSize.isZero()) + overrideSize = shape.mask.size; + + Vec2 point = {0, 0}; + + { + int rand0 = abs(RANDOM_KISS() * shape.mask.points.size()); + int index = MIN(rand0, shape.mask.points.size() - 1); + point = shape.mask.points[index]; + } + + _particleData.posx[i] = _sourcePosition.x + shape.x + point.x; + _particleData.posy[i] = _sourcePosition.y + shape.y + point.y; + + break; + } } } } @@ -1141,6 +1165,32 @@ void ParticleSystem::setEmissionShape(unsigned short index, EmissionShape shape) iter->second = shape; } +EmissionShape ParticleSystem::createMaskShape(std::string_view maskName, + Vec2 pos, + Vec2 overrideSize, + Vec2 scale, + float angle) +{ + EmissionShape shape{}; + + shape.type = EmissionShapeType::ALPHA_MASK; + + shape.mask = ParticleEmissionMaskCache::getInstance()->getEmissionMask(maskName); + + shape.x = pos.x; + shape.y = pos.y; + + shape.innerWidth = overrideSize.x; + shape.innerHeight = overrideSize.y; + + shape.outerWidth = scale.x; + shape.outerHeight = scale.y; + + shape.coneOffset = angle; + + return shape; +} + EmissionShape ParticleSystem::createPointShape(Vec2 pos) { EmissionShape shape{}; @@ -2232,4 +2282,87 @@ void ParticleSystem::setTimeScale(float scale) _timeScale = scale; } -NS_CC_END +static ParticleEmissionMaskCache* emissionMaskCache; + +ParticleEmissionMaskCache* ParticleEmissionMaskCache::getInstance() +{ + if (emissionMaskCache == nullptr) + { + emissionMaskCache = new ParticleEmissionMaskCache(); + return emissionMaskCache; + } + return emissionMaskCache; +} + +void ParticleEmissionMaskCache::bakeEmissionMask(std::string_view maskName, + std::string_view texturePath, + float alphaThreshold, + bool inverted) +{ + auto img = new Image(); + img->Image::initWithImageFile(texturePath); + img->autorelease(); + + CCASSERT(img, "image texture was nullptr."); + bakeEmissionMask(maskName, img, alphaThreshold, inverted); +} + +void ParticleEmissionMaskCache::bakeEmissionMask(std::string_view maskName, + Image* imageTexture, + float alphaThreshold, + bool inverted) +{ + auto img = imageTexture; + CCASSERT(img, "image texture was nullptr."); + CCASSERT(img->hasAlpha(), "image data should contain an alpha channel."); + + vector points; + + auto data = img->getData(); + auto w = img->getWidth(); + auto h = img->getHeight(); + + for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) + { + float a = data[(y * w + x) * 4 + 3] / 255.0F; + if (a >= alphaThreshold && !inverted) + points.push_back({float(x), float(h - y)}); + if (a < alphaThreshold && inverted) + points.push_back({float(x), float(h - y)}); + } + + auto iter = this->masks.find(maskName); + if (iter == this->masks.end()) + iter = this->masks.emplace(maskName, ParticleEmissionMaskDescriptor{}).first; + + ParticleEmissionMaskDescriptor desc; + desc.size = {float(w), float(h)}; + desc.points = std::move(points); + + iter->second = desc; +} + +const ParticleEmissionMaskDescriptor& ParticleEmissionMaskCache::getEmissionMask(std::string_view maskName) +{ + auto iter = this->masks.find(maskName); + if (iter == this->masks.end()) + { + ParticleEmissionMaskDescriptor desc; + desc.size = {float(1), float(1)}; + desc.points = {{0, 0}}; + return desc; + } + return iter->second; +} + +void ParticleEmissionMaskCache::releaseMaskFromMemory(std::string_view maskName) +{ + this->masks.erase(maskName); +} + +void ParticleEmissionMaskCache::releaseAllMasksFromMemory() +{ + this->masks.clear(); +} + +NS_CC_END \ No newline at end of file diff --git a/core/2d/CCParticleSystem.h b/core/2d/CCParticleSystem.h index 7a26cb6e93..1d0fd0ce5f 100644 --- a/core/2d/CCParticleSystem.h +++ b/core/2d/CCParticleSystem.h @@ -65,7 +65,14 @@ enum class EmissionShapeType RECT, RECTTORUS, CIRCLE, - TORUS + TORUS, + ALPHA_MASK +}; + +struct ParticleEmissionMaskDescriptor +{ + Vec2 size; + std::vector points; }; /** @@ -90,6 +97,8 @@ struct EmissionShape float coneOffset; float coneAngle; float edgeElasticity; + + ParticleEmissionMaskDescriptor mask; }; /** @struct ParticleAnimationDescriptor @@ -244,6 +253,29 @@ public: } }; +class CC_DLL ParticleEmissionMaskCache : public cocos2d::Ref +{ +public: + static ParticleEmissionMaskCache* getInstance(); + + void bakeEmissionMask(std::string_view maskName, + std::string_view texturePath, + float alphaThreshold = 0.5F, + bool inverted = false); + + void bakeEmissionMask(std::string_view maskName, + Image* imageTexture, + float alphaThreshold = 0.5F, + bool inverted = false); + + const ParticleEmissionMaskDescriptor& getEmissionMask(std::string_view maskName); + + void releaseMaskFromMemory(std::string_view maskName); + void releaseAllMasksFromMemory(); + + hlookup::string_map masks; +}; + // typedef void (*CC_UPDATE_PARTICLE_IMP)(id, SEL, tParticle*, Vec2); class Texture2D; @@ -292,7 +324,6 @@ emitter.startSpin = 0; @endcode */ - class CC_DLL ParticleSystem : public Node, public TextureProtocol, public PlayableProtocol { public: @@ -1164,6 +1195,17 @@ public: */ void setEmissionShape(unsigned short index, EmissionShape shape); + /** Adds an emission shape of type mask to the system. + * The mask should be added using the ParticleEmissionMaskCache class. + * + * @param maskName Name of the emission mask. + * @param pos Position of the emission shape in local space. + * @param overrideSize Size of the emission mask in pixels, leave ZERO to use texture size. + * @param scale Scale of the emission mask, the size will be multiplied by the specified scale. + * @param angle Angle of the sampled points to be rotated in degrees. + */ + static EmissionShape createMaskShape(std::string_view maskName, Vec2 pos = Vec2::ZERO, Vec2 overrideSize = Vec2::ZERO, Vec2 scale = Vec2::ONE, float angle = 0.0F); + /** Adds an emission shape of type point to the system. * @param pos Position of the emission shape in local space. */