From 9fe0bbd7cad662a72ffcadc7d772918e100a8c64 Mon Sep 17 00:00:00 2001 From: Adam Bialogonski Date: Fri, 2 Jun 2023 18:09:24 +0100 Subject: Particle System Demo Change-Id: Ib5cc186c001952c8f2f74c2371575a08d94bb607 --- .../effects/fire-ring-effect-modifier.cpp | 102 ++++++++++ .../effects/fire-ring-effect-modifier.h | 88 +++++++++ .../effects/fire-ring-effect-source.cpp | 87 +++++++++ .../effects/fire-ring-effect-source.h | 55 ++++++ .../effects/image-effect-modifier.cpp | 77 ++++++++ .../effects/image-effect-modifier.h | 53 +++++ .../effects/image-effect-source.cpp | 119 +++++++++++ .../particle-system/effects/image-effect-source.h | 62 ++++++ .../particle-system/effects/particle-effect.cpp | 106 ++++++++++ examples/particle-system/effects/particle-effect.h | 65 ++++++ .../effects/sparkles-effect-modifier.cpp | 89 +++++++++ .../effects/sparkles-effect-modifier.h | 54 +++++ .../effects/sparkles-effect-source.cpp | 91 +++++++++ .../effects/sparkles-effect-source.h | 56 ++++++ .../particle-system/particle-system-example.cpp | 217 +++++++++++++++++++++ 15 files changed, 1321 insertions(+) create mode 100644 examples/particle-system/effects/fire-ring-effect-modifier.cpp create mode 100644 examples/particle-system/effects/fire-ring-effect-modifier.h create mode 100644 examples/particle-system/effects/fire-ring-effect-source.cpp create mode 100644 examples/particle-system/effects/fire-ring-effect-source.h create mode 100644 examples/particle-system/effects/image-effect-modifier.cpp create mode 100644 examples/particle-system/effects/image-effect-modifier.h create mode 100644 examples/particle-system/effects/image-effect-source.cpp create mode 100644 examples/particle-system/effects/image-effect-source.h create mode 100644 examples/particle-system/effects/particle-effect.cpp create mode 100644 examples/particle-system/effects/particle-effect.h create mode 100644 examples/particle-system/effects/sparkles-effect-modifier.cpp create mode 100644 examples/particle-system/effects/sparkles-effect-modifier.h create mode 100644 examples/particle-system/effects/sparkles-effect-source.cpp create mode 100644 examples/particle-system/effects/sparkles-effect-source.h create mode 100644 examples/particle-system/particle-system-example.cpp (limited to 'examples') diff --git a/examples/particle-system/effects/fire-ring-effect-modifier.cpp b/examples/particle-system/effects/fire-ring-effect-modifier.cpp new file mode 100644 index 00000000..56f7e5b2 --- /dev/null +++ b/examples/particle-system/effects/fire-ring-effect-modifier.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "fire-ring-effect-modifier.h" +#include "fire-ring-effect-source.h" + +namespace Dali::ParticleEffect +{ +FireModifier::FireModifier(ParticleEmitter& emitter) +: mEmitter(emitter) +{ + // initialize gradient with flame colors + mFireGradient.PushColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f), 1.0f - 1.0f); + mFireGradient.PushColor(Vector4(0.975, 0.955, 0.476, 1.0f), 1.0f - 0.947f); + mFireGradient.PushColor(Vector4(0.999, 0.550, 0.194, 1.0f), 1.0f - 0.800f); + mFireGradient.PushColor(Vector4(0.861, 0.277, 0.094, 1.0f), 1.0f - 0.670f); + mFireGradient.PushColor(Vector4(0.367, 0.0, 0.0, 1.0f), 1.0f - 0.456f); + mFireGradient.PushColor(Vector4(0.3, 0.3, 0.3, 1.0f), 1.0f - 0.400f); + mFireGradient.PushColor(Vector4(0.3, 0.2, 0.2, 1.0f), 1.0f - 0.200f); + mFireGradient.PushColor(Vector4(0.2, 0.1, 0.1, 1.0f), 1.0f - 0.150f); + mFireGradient.PushColor(Vector4(0.1, 0.0, 0.0, 1.0f), 1.0f- 0.100f); + mFireGradient.PushColor(Vector4(0.0, 0.0, 0.0, 0.5f), 1.0f-0.050f); + mFireGradient.PushColor(Vector4(0.0, 0.0, 0.0, 0.2f), 1.0f); +} + +bool FireModifier::IsMultiThreaded() +{ + return false; +} + +void FireModifier::Update(ParticleList& particleList, uint32_t first, uint32_t count) +{ + // If no acive particles return + if(!particleList.GetActiveParticleCount()) + { + return; + } + + mAngle = ((mAngle + 2) % 360); + + // Retrieve the Source and get the stream + if(!mStreamBasePos) + { + mStreamBasePos = static_cast(&mEmitter.GetSource().GetSourceCallback())->mStreamBasePos; + } + + // Missing stream, return! + if(!mStreamBasePos) + { + return; + } + + auto& activeParticles = particleList.GetActiveParticles(); + + auto it = activeParticles.begin(); + std::advance(it, first); + + int i = 0; + for(; count; ++it, count--) + { + i += 1; + + // Acquire stream data + auto& particle = *it; + auto& position = particle.Get(ParticleStream::POSITION_STREAM_BIT); + auto& velocity = particle.Get(ParticleStream::VELOCITY_STREAM_BIT); + auto& color = particle.Get(ParticleStream::COLOR_STREAM_BIT); + auto& baseLifetime = particle.Get(ParticleStream::LIFETIME_BASE_STREAM_BIT); + auto& scale = particle.Get(ParticleStream::SCALE_STREAM_BIT); + + // Get base positions + auto& basePos = particle.GetByIndex(mStreamBasePos); + + float lifetime = particle.Get(ParticleStream::LIFETIME_STREAM_BIT); + position.y += -fabs(velocity.y); + position.x = basePos.x + 5.0f * sin((((mAngle + i)%360)*M_PI)/180.f ); + + velocity *= 0.990f; + auto newColor = mFireGradient.GetColorAt((baseLifetime - lifetime) / baseLifetime); + float normalizedTime = (lifetime / baseLifetime); + newColor.a = normalizedTime * normalizedTime; + + scale = Vector3(64.0f*(normalizedTime * normalizedTime * normalizedTime * normalizedTime), 64.0f*(normalizedTime * normalizedTime * normalizedTime * normalizedTime), 1.0); + + color = newColor; + } +} +} \ No newline at end of file diff --git a/examples/particle-system/effects/fire-ring-effect-modifier.h b/examples/particle-system/effects/fire-ring-effect-modifier.h new file mode 100644 index 00000000..3f9f6ea4 --- /dev/null +++ b/examples/particle-system/effects/fire-ring-effect-modifier.h @@ -0,0 +1,88 @@ +#ifndef DALI_PROJECT_FIRE_RING_EFFECT_MODIFIER_H +#define DALI_PROJECT_FIRE_RING_EFFECT_MODIFIER_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +namespace Dali::ParticleEffect +{ +using namespace Dali::Toolkit::ParticleSystem; + +class FireModifier : public ParticleModifierInterface +{ +public: + + struct ColorGradient + { + std::vector colors; + std::vector position; + + void PushColor(const Vector4& color, float pos) + { + colors.emplace_back(color); + position.emplace_back(pos); + } + + Vector4 GetColorAt(float pos) + { + if(pos >= 1.0f) + { + return colors.back(); + } + else if(pos <= 0.0f) + { + return colors[0]; + } + for(auto i = 0u; i < position.size() - 1; ++i) + { + if(pos >= position[i] && pos < position[i + 1]) + { + auto colorDiff = colors[i + 1] - colors[i]; + return colors[i] + (colorDiff * ((pos - position[i]) / (position[i + 1] - position[i]))); + } + } + return colors[0]; + } + }; + + + explicit FireModifier(ParticleEmitter& emitter); + + bool IsMultiThreaded() override; + + void Update(ParticleList& particleList, uint32_t first, uint32_t count) override; + + ColorGradient mFireGradient; + ParticleEmitter mEmitter; + uint32_t mStreamBasePos{0u}; + uint32_t mAngle{0u}; +}; + + + +} + +#endif // DALI_PROJECT_FIRE_RING_EFFECT_MODIFIER_H diff --git a/examples/particle-system/effects/fire-ring-effect-source.cpp b/examples/particle-system/effects/fire-ring-effect-source.cpp new file mode 100644 index 00000000..0ff0652a --- /dev/null +++ b/examples/particle-system/effects/fire-ring-effect-source.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "fire-ring-effect-source.h" +#include + +namespace Dali::ParticleEffect +{ +static float LIFETIME = 5.0f; +FireSource::FireSource(ParticleEmitter& emitter) +: mEmitter(emitter) +{ + std::time_t result = std::time(nullptr); + srand(result); + mRadius = Vector2::ONE; +} + +FireSource::FireSource(ParticleEmitter& emitter, Dali::Vector2 ringRadius) : +mEmitter(emitter) +{ + std::time_t result = std::time(nullptr); + srand(result); + mRadius = ringRadius; + +} + +void FireSource::Init() +{ + mStreamBasePos = mEmitter.GetParticleList().AddLocalStream(Vector3::ZERO); +} + +uint32_t FireSource::Update(ParticleList& particleList, uint32_t count) +{ + while(count--) + { + auto particle = particleList.NewParticle(LIFETIME * (float(std::rand() % 1000)/1000.0f) + 1.0f ); + if(!particle) + { + return 0u; + } + + auto& basePosition = particle.GetByIndex(mStreamBasePos); + + auto& position = particle.Get(ParticleStream::POSITION_STREAM_BIT); + auto& color = particle.Get(ParticleStream::COLOR_STREAM_BIT); + auto& velocity = particle.Get(ParticleStream::VELOCITY_STREAM_BIT); + auto& scale = particle.Get(ParticleStream::SCALE_STREAM_BIT); + UpdateParticle(position, basePosition, color, velocity, scale); + } + + return 0; +} + +void FireSource::UpdateParticle(Vector3& position, Vector3& basePosition, Vector4& color, Vector3& velocity, Vector3& scale) +{ + float posRadians = ((rand() % 360) * M_PI) / 180.0f; + + basePosition.x = position.x = mRadius.x * sin(posRadians); + basePosition.y = position.y = mRadius.y * cos(posRadians); + color = Dali::Color::WHITE; // white color when emitted + + // angle of motion + float radians = ((rand() % 360) * M_PI) / 180.0f; + float speed = ((rand() % 5) + 5); + velocity.x = sin(radians) * speed; + velocity.y = cos(radians) * speed; + + // Random initial scale + float currentScale = float(rand() % 32) + 32; + scale = Vector3(currentScale, currentScale, 1); +} + +} // namespace Dali::ParticleEffect \ No newline at end of file diff --git a/examples/particle-system/effects/fire-ring-effect-source.h b/examples/particle-system/effects/fire-ring-effect-source.h new file mode 100644 index 00000000..28f78efa --- /dev/null +++ b/examples/particle-system/effects/fire-ring-effect-source.h @@ -0,0 +1,55 @@ +#ifndef DALI_FIRE_RING_EFFECT_SOURCE_H +#define DALI_FIRE_RING_EFFECT_SOURCE_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include + +namespace Dali::ParticleEffect +{ +using namespace Dali::Toolkit::ParticleSystem; + +class FireSource : public Toolkit::ParticleSystem::ParticleSourceInterface +{ +public: + + explicit FireSource(ParticleEmitter& emitter); + + explicit FireSource(ParticleEmitter& emitter, Dali::Vector2 ringRadius); + + uint32_t Update(ParticleList& particleList, uint32_t count) override; + + void Init() override; + + void UpdateParticle(Vector3& position, Vector3& basePosition, Vector4& color, Vector3& velocity, Vector3& scale); + + ParticleEmitter mEmitter; + + Dali::Vector2 mRadius; + + uint32_t mStreamBasePos{0u}; + +}; + +} +#endif // DALI_FIRE_RING_EFFECT_SOURCE_H diff --git a/examples/particle-system/effects/image-effect-modifier.cpp b/examples/particle-system/effects/image-effect-modifier.cpp new file mode 100644 index 00000000..395040c7 --- /dev/null +++ b/examples/particle-system/effects/image-effect-modifier.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "image-effect-modifier.h" +#include "image-effect-source.h" +namespace Dali::ParticleEffect +{ + +#define RAD(x) (float(x)*M_PI/180.0f) + +ImageExplodeEffectModifier::ImageExplodeEffectModifier(ParticleEmitter& emitter) +: mEmitter(emitter) +{ + +} + +bool ImageExplodeEffectModifier::IsMultiThreaded() +{ + return false; +} + +void ImageExplodeEffectModifier::Update(ParticleList& particleList, uint32_t first, uint32_t count) +{ + // If no acive particles return + if(!particleList.GetActiveParticleCount()) + { + return; + } + + // Retrieve the Source and get the stream + if(!mStreamBasePos) + { + mStreamBasePos = static_cast(&mEmitter.GetSource().GetSourceCallback())->mStreamBasePos; + } + + // Missing stream, return! + if(!mStreamBasePos) + { + return; + } + + auto& activeParticles = particleList.GetActiveParticles(); + + auto it = activeParticles.begin(); + std::advance(it, first); + + mAngle += 5.0f; + + for(; count; ++it, count--) + { + // Acquire stream data + auto& particle = *it; + auto& position = particle.Get(ParticleStream::POSITION_STREAM_BIT); + auto& color = particle.Get(ParticleStream::COLOR_STREAM_BIT); + + // Get base positions + auto& basePos = particle.GetByIndex(mStreamBasePos); + position.z = 200.f * sin(RAD(mAngle+basePos.x)); + color.a = position.z < 0.0f ? 1.0f : 1.0f - position.z/500.0f; + position.z = 500 + position.z; + } +} +} \ No newline at end of file diff --git a/examples/particle-system/effects/image-effect-modifier.h b/examples/particle-system/effects/image-effect-modifier.h new file mode 100644 index 00000000..8a0781e4 --- /dev/null +++ b/examples/particle-system/effects/image-effect-modifier.h @@ -0,0 +1,53 @@ +#ifndef DALI_IMAGE_EFFECT_MODIFIER_H +#define DALI_IMAGE_EFFECT_MODIFIER_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +#include +#include +#include +#include +#include +#include + +namespace Dali::ParticleEffect +{ +using namespace Dali::Toolkit::ParticleSystem; + +class ImageExplodeEffectModifier : public ParticleModifierInterface +{ +public: + + explicit ImageExplodeEffectModifier(ParticleEmitter& emitter); + + bool IsMultiThreaded() override; + + void Update(ParticleList& particleList, uint32_t first, uint32_t count) override; + + ParticleEmitter mEmitter; + uint32_t mStreamBasePos{0u}; + float mAngle{0.0f}; + +}; + + + +} + +#endif // DALI_IMAGE_EFFECT_MODIFIER_H diff --git a/examples/particle-system/effects/image-effect-source.cpp b/examples/particle-system/effects/image-effect-source.cpp new file mode 100644 index 00000000..2e52a1e2 --- /dev/null +++ b/examples/particle-system/effects/image-effect-source.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "image-effect-source.h" +#include +#include +#include + +namespace Dali::ParticleEffect +{ +namespace +{ +Vector4 GetColorAt(uint32_t x, uint32_t y, Devel::PixelBuffer& buffer) +{ + if(buffer.GetPixelFormat() == Pixel::Format::RGBA8888) + { + const auto ptr = reinterpret_cast(buffer.GetBuffer()); + auto value = *(ptr + x + (y * buffer.GetHeight())); + auto rgba = reinterpret_cast(&value); + return Vector4(float(rgba[0]) / 255.0f, float(rgba[1]) / 255.0f, float(rgba[2]) / 255.0f, 1.0f); + } + else + { + auto rgba = reinterpret_cast(buffer.GetBuffer() + (y*buffer.GetWidth()*3) + (x*3)); + return Vector4(float(rgba[0]) / 255.0f, float(rgba[1]) / 255.0f, float(rgba[2]) / 255.0f, 1.0f); + } +} +} + +static float LIFETIME = 50000.0f; // we need infinite lifetime? +ImageExplodeEffectSource::ImageExplodeEffectSource(ParticleEmitter& emitter) +: mEmitter(emitter) +{ +} + +ImageExplodeEffectSource::ImageExplodeEffectSource(ParticleEmitter& emitter, const std::string& imageFileName, uint32_t width, uint32_t height) : +mEmitter(emitter) +{ + + // Create texture + std::string filePath(DEMO_IMAGE_DIR); + filePath += imageFileName; + ImageDimensions dimensions(width, height); + // Pixel buffer will be used as a source of pixels (populating colors of particles based on image pixels) + Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromFile(filePath, dimensions, FittingMode::SHRINK_TO_FIT, SamplingMode::DEFAULT, false); + mImageWidth = pixelBuffer.GetWidth(); + mImageHeight = pixelBuffer.GetHeight(); + mPixelBuffer = pixelBuffer; +} + +void ImageExplodeEffectSource::Init() +{ + mStreamBasePos = mEmitter.GetParticleList().AddLocalStream(Vector3::ZERO); +} + +uint32_t ImageExplodeEffectSource::Update(ParticleList& particleList, uint32_t count) +{ + if(!mShouldEmit) + { + return 0; + } + + if(mPixelBuffer.GetPixelFormat() != Dali::Pixel::RGBA8888 && + mPixelBuffer.GetPixelFormat() != Dali::Pixel::RGB888) + { + return 0; + } + + auto i = 0u; + float particleScale = 4.0f; + float pixelSize = 2.0f; + + uint32_t halfWidth = (mImageWidth/2) * particleScale; + uint32_t halfHeight = (mImageHeight/2) * particleScale; + + for(auto y = 0u ; y < mImageHeight; ++y) + { + for(auto x = 0u; x < mImageWidth; ++x) + { + if(i < particleList.GetCapacity()) + { + // Ignore count, populating all pixels instantly (emitter must account for all the points) + auto particle = particleList.NewParticle(LIFETIME); + + auto& basePosition = particle.GetByIndex(mStreamBasePos); + auto& position = particle.Get(ParticleStream::POSITION_STREAM_BIT); + auto& color = particle.Get(ParticleStream::COLOR_STREAM_BIT); + auto& velocity = particle.Get(ParticleStream::VELOCITY_STREAM_BIT); + auto& scale = particle.Get(ParticleStream::SCALE_STREAM_BIT); + color = GetColorAt(x, y, mPixelBuffer); + // Set basePosition + position = basePosition = Vector3(x* particleScale -halfWidth, y* particleScale -halfHeight, 0); + scale = Vector3(pixelSize, pixelSize, 1); + velocity = Vector3::ZERO; + } + ++i; + } + } + + mShouldEmit = false; + return mImageWidth * mImageHeight; +} + + +} // namespace Dali::ParticleEffect \ No newline at end of file diff --git a/examples/particle-system/effects/image-effect-source.h b/examples/particle-system/effects/image-effect-source.h new file mode 100644 index 00000000..49d84c91 --- /dev/null +++ b/examples/particle-system/effects/image-effect-source.h @@ -0,0 +1,62 @@ +#ifndef DALI_IMAGE_EFFECT_SOURCE_H +#define DALI_IMAGE_EFFECT_SOURCE_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace Dali::ParticleEffect +{ +using namespace Dali::Toolkit::ParticleSystem; + +/** + * Image source will use 2D image to populate points for the emitter + * This particular implementation populates points only once. + */ +class ImageExplodeEffectSource : public Toolkit::ParticleSystem::ParticleSourceInterface +{ +public: + + explicit ImageExplodeEffectSource(ParticleEmitter& emitter); + + explicit ImageExplodeEffectSource(ParticleEmitter& emitter, const std::string& imageFileName, uint32_t width, uint32_t height); + + uint32_t Update(ParticleList& particleList, uint32_t count) override; + + void Init() override; + + ParticleEmitter mEmitter; + + uint32_t mImageWidth{0u}; + uint32_t mImageHeight{0u}; + + uint32_t mStreamBasePos{0u}; + + Devel::PixelBuffer mPixelBuffer; + + bool mShouldEmit {true}; +}; + +} +#endif // DALI_IMAGE_EFFECT_SOURCE_H diff --git a/examples/particle-system/effects/particle-effect.cpp b/examples/particle-system/effects/particle-effect.cpp new file mode 100644 index 00000000..e4f2c302 --- /dev/null +++ b/examples/particle-system/effects/particle-effect.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "particle-effect.h" +#include "fire-ring-effect-source.h" +#include "sparkles-effect-source.h" +#include "image-effect-source.h" +#include "fire-ring-effect-modifier.h" +#include "sparkles-effect-modifier.h" +#include "image-effect-modifier.h" + +#include +#include +#include +#include +#include + +#ifndef DEMO_IMAGE_DIR +#define DEMO_IMAGE_DIR "" +#endif + +namespace Dali::ParticleEffect +{ +using ParticleEmitter = Dali::Toolkit::ParticleSystem::ParticleEmitter; +using ParticleSource = Dali::Toolkit::ParticleSystem::ParticleSource; +using ParticleModifier = Dali::Toolkit::ParticleSystem::ParticleModifier; + +struct FunctorReturn +{ + ParticleEmitter emitter; + ParticleSource source; + ParticleModifier modifier; +}; + +static std::vector gEffectInitializers = +{ + [](const ParticleEffectParams& params){ + ParticleEmitter emitter = ParticleEmitter::New(); + return FunctorReturn{emitter, ParticleSource::New(emitter, params.sourceSize), ParticleModifier::New(emitter) }; + }, + [](const ParticleEffectParams& params){ + ParticleEmitter emitter = ParticleEmitter::New(); + return FunctorReturn{emitter, ParticleSource::New(emitter), ParticleModifier::New(emitter) }; + }, + [](const ParticleEffectParams& params){ + ParticleEmitter emitter = ParticleEmitter::New(); + return FunctorReturn{emitter, ParticleSource::New(emitter, + params.strImageSourceName, + uint32_t(params.sourceSize.width), + uint32_t(params.sourceSize.height) + ), ParticleModifier::New(emitter) }; + }, +}; + +ParticleEffect::ParticleEffect() = default; + +ParticleEffect::~ParticleEffect() = default; + +Dali::Toolkit::ParticleSystem::ParticleEmitter ParticleEffect::CreateEffectEmitter( EffectType effectType, Actor parentActor, const ParticleEffectParams& params ) +{ + auto retval = gEffectInitializers[int(effectType)](params); + auto emitter = retval.emitter; + + ParticleRenderer renderer = ParticleRenderer::New(); + + if(!params.strTexture.empty()) + { + // Create texture + std::string filename(DEMO_IMAGE_DIR); + filename += params.strTexture; + Dali::PixelData pixelData = Dali::Toolkit::SyncImageLoader::Load(filename); + auto texture = Texture::New(Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight()); + texture.Upload(pixelData); + renderer.SetTexture(texture); + } + + emitter.AttachTo(std::move(parentActor)); + emitter.SetEmissionRate( params.emissionRate ); // 20 particles emitted per second + emitter.SetParticleCount( params.particleCount ); + emitter.SetSource( retval.source ); + emitter.SetDomain( ParticleDomain::New() ); + emitter.AddModifier(retval.modifier); + emitter.SetRenderer( renderer ); + renderer.SetBlendingMode(Dali::Toolkit::ParticleSystem::BlendingMode::SCREEN); + emitter.SetInitialParticleCount( params.initialParticleCount ); + + return emitter; +} + + +} + diff --git a/examples/particle-system/effects/particle-effect.h b/examples/particle-system/effects/particle-effect.h new file mode 100644 index 00000000..a1277ef2 --- /dev/null +++ b/examples/particle-system/effects/particle-effect.h @@ -0,0 +1,65 @@ +#ifndef DALI_PARTICLE_EFFECT_H +#define DALI_PARTICLE_EFFECT_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +namespace Dali::ParticleEffect +{ + +using EffectIndex = uint32_t; + +enum class EffectType +{ + FIRE_RING, + SPARKLES, + IMAGE_EXPLOSION +}; + +struct ParticleEffectParams +{ + uint32_t emissionRate; + uint32_t particleCount; + uint32_t initialParticleCount; + Vector2 sourceSize; + std::string strTexture; + std::string strImageSourceName; +}; + +/** + * Simple manager to spawn and control partcile emitters + */ +class ParticleEffect +{ +public: + + ParticleEffect(); + + ~ParticleEffect(); + + Dali::Toolkit::ParticleSystem::ParticleEmitter CreateEffectEmitter( EffectType effectType, Actor parentActor, const ParticleEffectParams& params ); + +private: + + +}; +} + +#endif // DALI_PARTICLE_MANAGER_H \ No newline at end of file diff --git a/examples/particle-system/effects/sparkles-effect-modifier.cpp b/examples/particle-system/effects/sparkles-effect-modifier.cpp new file mode 100644 index 00000000..dda93da9 --- /dev/null +++ b/examples/particle-system/effects/sparkles-effect-modifier.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "sparkles-effect-modifier.h" +#include "sparkles-effect-source.h" + +namespace Dali::ParticleEffect +{ +static float LIFETIME = 3.0f; +SparklesModifier::SparklesModifier(ParticleEmitter& emitter) +: mEmitter(emitter) +{ +} + +bool SparklesModifier::IsMultiThreaded() +{ + return true; +} + +void SparklesModifier::Update(ParticleList& particleList, uint32_t first, uint32_t count) +{ + // If no acive particles return + if(!particleList.GetActiveParticleCount()) + { + return; + } + + mAngle = ((mAngle + 2) % 360); + + // Retrieve the Source and get the stream + if(!mStreamBasePos) + { + mStreamBasePos = static_cast(&mEmitter.GetSource().GetSourceCallback())->mStreamBasePos; + } + if(!mStreamBaseAngle) + { + mStreamBaseAngle = static_cast(&mEmitter.GetSource().GetSourceCallback())->mStreamBaseAngle; + } + + // Missing stream, return! + if(!mStreamBasePos) + { + return; + } + + auto& activeParticles = particleList.GetActiveParticles(); + + auto it = activeParticles.begin(); + std::advance(it, first); + + for(; count; ++it, count--) + { + // Acquire stream data + auto& particle = *it; + auto& position = particle.Get(ParticleStream::POSITION_STREAM_BIT); + auto& velocity = particle.Get(ParticleStream::VELOCITY_STREAM_BIT); + auto& color = particle.Get(ParticleStream::COLOR_STREAM_BIT); + auto& scale = particle.Get(ParticleStream::SCALE_STREAM_BIT); + + // Get base positions + [[maybe_unused]] auto& basePos = particle.GetByIndex(mStreamBasePos); + + auto angle = particle.GetByIndex(mStreamBaseAngle); + auto radians = ((angle * M_PI)/180.f); + float lifetime = particle.Get(ParticleStream::LIFETIME_STREAM_BIT); + position.y += velocity.y *sin(radians); + position.x += velocity.x * cos(radians); + + velocity *= 0.990f; + float normalizedTime = (lifetime / LIFETIME); + color.a = normalizedTime; + scale = Vector3(64.0f*(normalizedTime * normalizedTime * normalizedTime * normalizedTime), 64.0f*(normalizedTime * normalizedTime * normalizedTime * normalizedTime), 1.0); + } +} +} \ No newline at end of file diff --git a/examples/particle-system/effects/sparkles-effect-modifier.h b/examples/particle-system/effects/sparkles-effect-modifier.h new file mode 100644 index 00000000..1c64bd63 --- /dev/null +++ b/examples/particle-system/effects/sparkles-effect-modifier.h @@ -0,0 +1,54 @@ +#ifndef DALI_PARTICLES_SPARKLES_EFFECT_MODIFIER_H +#define DALI_PARTICLES_SPARKLES_EFFECT_MODIFIER_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +namespace Dali::ParticleEffect +{ +using namespace Dali::Toolkit::ParticleSystem; + +class SparklesModifier : public ParticleModifierInterface +{ +public: + + explicit SparklesModifier(ParticleEmitter& emitter); + + bool IsMultiThreaded() override; + + void Update(ParticleList& particleList, uint32_t first, uint32_t count) override; + + ParticleEmitter mEmitter; + uint32_t mStreamBasePos{0u}; + uint32_t mStreamBaseAngle{0u}; + uint32_t mAngle{0u}; +}; + + + +} + +#endif // DALI_PARTICLES_SPARKLES_EFFECT_MODIFIER_H diff --git a/examples/particle-system/effects/sparkles-effect-source.cpp b/examples/particle-system/effects/sparkles-effect-source.cpp new file mode 100644 index 00000000..56eb2987 --- /dev/null +++ b/examples/particle-system/effects/sparkles-effect-source.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "sparkles-effect-source.h" + +namespace Dali::ParticleEffect +{ +static float LIFETIME = 3.0f; +SparklesSource::SparklesSource(ParticleEmitter& emitter) +: mEmitter(emitter) +{ + std::time_t result = std::time(nullptr); + srand(result); + mRadius = Vector2::ONE; +} + +SparklesSource::SparklesSource(ParticleEmitter& emitter, Dali::Vector2 ringRadius) : +mEmitter(emitter) +{ + std::time_t result = std::time(nullptr); + srand(result); + mRadius = ringRadius; + +} + +void SparklesSource::Init() +{ + mStreamBasePos = mEmitter.GetParticleList().AddLocalStream(Vector3::ZERO); + mStreamBaseAngle = mEmitter.GetParticleList().AddLocalStream(0.0f); +} + +uint32_t SparklesSource::Update(ParticleList& particleList, uint32_t count) +{ + while(count--) + { + auto particle = particleList.NewParticle(LIFETIME); + if(!particle) + { + return 0u; + } + + auto& basePosition = particle.GetByIndex(mStreamBasePos); + auto& angle = particle.GetByIndex(mStreamBaseAngle); + auto& position = particle.Get(ParticleStream::POSITION_STREAM_BIT); + auto& color = particle.Get(ParticleStream::COLOR_STREAM_BIT); + auto& velocity = particle.Get(ParticleStream::VELOCITY_STREAM_BIT); + auto& scale = particle.Get(ParticleStream::SCALE_STREAM_BIT); + + + UpdateParticle(position, basePosition, color, velocity, scale, angle); + } + + return 0; +} + +void SparklesSource::UpdateParticle(Vector3& position, Vector3& basePosition, Vector4& color, Vector3& velocity, Vector3& scale, float& angle) +{ + static uint32_t a = 0.0f; + float posRadians = ((rand() % 360) * M_PI) / 180.0f; + + basePosition.x = position.x = mRadius.x * sin(posRadians); + basePosition.y = position.y = mRadius.y * cos(posRadians); + color = Dali::Color::WHITE; + + angle = float(a); + a = ((a+5)%360); + float rad = ((rand() % 360) * M_PI) / 180.0f; + float speed = ((rand() % 5) + 5); + velocity.x = sin(rad) * speed; + velocity.y = cos(rad) * speed; + + // Random initial scale + float initialScale = float(rand() % 32) + 32; + scale = Vector3(initialScale, initialScale, 1); +} + +} // namespace Dali::ParticleEffect \ No newline at end of file diff --git a/examples/particle-system/effects/sparkles-effect-source.h b/examples/particle-system/effects/sparkles-effect-source.h new file mode 100644 index 00000000..466c49b9 --- /dev/null +++ b/examples/particle-system/effects/sparkles-effect-source.h @@ -0,0 +1,56 @@ +#ifndef DALI_PARTICLES_SPARKLES_EFFECT_SOURCE_H +#define DALI_PARTICLES_SPARKLES_EFFECT_SOURCE_H + +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include + +namespace Dali::ParticleEffect +{ +using namespace Dali::Toolkit::ParticleSystem; + +class SparklesSource : public Toolkit::ParticleSystem::ParticleSourceInterface +{ +public: + + explicit SparklesSource(ParticleEmitter& emitter); + + explicit SparklesSource(ParticleEmitter& emitter, Dali::Vector2 ringRadius); + + uint32_t Update(ParticleList& particleList, uint32_t count) override; + + void Init() override; + + void UpdateParticle(Vector3& position, Vector3& basePosition, Vector4& color, Vector3& velocity, Vector3& scale, float& angle); + + ParticleEmitter mEmitter; + + Dali::Vector2 mRadius; + + uint32_t mStreamBasePos{0u}; + uint32_t mStreamBaseAngle{0u}; + +}; + +} +#endif // DALI_PARTICLES_SPARKLES_EFFECT_SOURCE_H diff --git a/examples/particle-system/particle-system-example.cpp b/examples/particle-system/particle-system-example.cpp new file mode 100644 index 00000000..7d6a8898 --- /dev/null +++ b/examples/particle-system/particle-system-example.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "effects/particle-effect.h" + +using namespace Dali; +using namespace Dali::Toolkit::ParticleSystem; +using namespace Dali::Toolkit; +using Dali::Toolkit::TextLabel; + +using namespace Dali::ParticleEffect; + +/** + * This example shows Particle System feature + */ +class ParticleEffectController : public ConnectionTracker +{ +public: + + ParticleEffectController(Application& application) + : mApplication(application) + { + // Connect to the Application's Init signal + mApplication.InitSignal().Connect(this, &ParticleEffectController::Create); + } + + ~ParticleEffectController() = default; // Nothing to do in destructor + + template + ButtonType MakeButton( std::string title, + Vector2 position, + Vector2 size, + bool toggleable, + std::function onClick ) + { + ButtonType button = ButtonType::New(); + button.SetProperty( Button::Property::LABEL, title); + button.SetProperty( Actor::Property::POSITION, position); + button.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + button.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + button.SetProperty(Button::Property::TOGGLABLE, toggleable); + static std::map> callbackMap; + struct OnClick + { + static bool Slot(Button btn) + { + auto ptr = btn.GetObjectPtr(); + return callbackMap[ptr](btn); + } + }; + + mUILastControlPosition = position; + mUILastControlSize = (size == Vector2::ZERO ? Vector2(button.GetNaturalSize()) : size); + + callbackMap[button.GetObjectPtr()] = onClick; + button.ClickedSignal().Connect(OnClick::Slot); + return button; + } + + // The Init signal is received once (only) during the Application lifetime + void Create(Application& application) + { + using namespace Dali::ParticleEffect; + + // Get a handle to the window + Window window = application.GetWindow(); + window.SetBackgroundColor(Color::BLACK); + { + Actor emitterActor = Actor::New(); + emitterActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + emitterActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + emitterActor.SetProperty(Actor::Property::POSITION, Vector2(0.0, 0.0f)); + emitterActor.SetProperty(Actor::Property::SIZE, Vector2(1.0, 1.0f)); + window.Add(emitterActor); + + mEmitterActor = emitterActor; + PushButton lastButton; + window.Add( + MakeButton("Fire Effect", Vector2::ZERO, {}, true, [&](Button button){ + + if(mCurrentEmitter) + { + mCurrentEmitter.Stop(); + mCurrentEmitter.Reset(); + } + + ParticleEffectParams params{}; + params.particleCount = 5000; + params.emissionRate = 1000; + params.initialParticleCount = 0; + params.sourceSize = Vector2(200, 10); + params.strTexture = "sparkle-part1.png"; + + mCurrentEmitter = mParticleSystem->CreateEffectEmitter( EffectType::FIRE_RING, mEmitterActor, params ); + mCurrentEmitter.Start(); + return true; + }) + ); + + window.Add( + MakeButton("Sparkle Effect", Vector2(0.0f, mUILastControlSize.height), {}, true, [&](Button button){ + if(mCurrentEmitter) + { + mCurrentEmitter.Stop(); + mCurrentEmitter.Reset(); + } + + ParticleEffectParams params{}; + params.particleCount = 10000; + params.emissionRate = 500; + params.initialParticleCount = 0; + params.sourceSize = Vector2(10, 10); + params.strTexture = "blue-part2.png"; + + mCurrentEmitter = mParticleSystem->CreateEffectEmitter( EffectType::SPARKLES, mEmitterActor, params ); + mCurrentEmitter.Start(); + return true; + }) + ); + + window.Add( + MakeButton("Image Source Effect", Vector2(0.0f, mUILastControlPosition.y + mUILastControlSize.height), {}, true, [&](Button button){ + if(mCurrentEmitter) + { + mCurrentEmitter.Stop(); + mCurrentEmitter.Reset(); + } + + ParticleEffectParams params{}; + params.particleCount = 20000; + params.emissionRate = 0; + params.initialParticleCount = 10; + params.sourceSize = Vector2(64, 64); + params.strImageSourceName = "particle-image-source.jpg"; + + mCurrentEmitter = mParticleSystem->CreateEffectEmitter( EffectType::IMAGE_EXPLOSION, mEmitterActor, params ); + mCurrentEmitter.Start(); + return true; + }) + ); + window.Add( + MakeButton("Quit", Vector2(0.0f, mUILastControlPosition.y + mUILastControlSize.height * 2), {}, true, [&](Button button){ + if(mCurrentEmitter) + { + mCurrentEmitter.Stop(); + mCurrentEmitter.Reset(); + } + mApplication.Quit(); + return true; + }) + ); + } + + // Respond to key events + window.KeyEventSignal().Connect(this, &ParticleEffectController::OnKeyEvent); + } + + void OnKeyEvent(const KeyEvent& event) + { + if(event.GetState() == KeyEvent::DOWN) + { + if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK)) + { + mApplication.Quit(); + } + } + } + +private: + + Application& mApplication; + std::unique_ptr mParticleSystem; + ParticleEmitter mCurrentEmitter; + Actor mEmitterActor; + + // Needed for buttons + Vector2 mUILastControlPosition; + Vector2 mUILastControlSize; + +}; + +int DALI_EXPORT_API main(int argc, char** argv) +{ + Application application = Application::New(&argc, &argv); + ParticleEffectController test(application); + application.MainLoop(); + return 0; +} -- cgit v1.2.3