From: Adeel Kazmi Date: Thu, 5 Nov 2020 22:14:00 +0000 (+0000) Subject: Merge "Reduced complexity of dali-table-view" into devel/master X-Git-Tag: dali_2.0.0~1 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-demo.git;a=commitdiff_plain;h=e22394f525ccad9b97ab0fcb7869ce62ab66dc34;hp=a6bac419bd77059181edcf4af35cbc1b3e30296c Merge "Reduced complexity of dali-table-view" into devel/master --- diff --git a/com.samsung.dali-demo.xml b/com.samsung.dali-demo.xml index 10425fb..174f29c 100644 --- a/com.samsung.dali-demo.xml +++ b/com.samsung.dali-demo.xml @@ -169,6 +169,9 @@ + + + diff --git a/demo/dali-demo.cpp b/demo/dali-demo.cpp index eecd9ad..677c4de 100644 --- a/demo/dali-demo.cpp +++ b/demo/dali-demo.cpp @@ -50,6 +50,7 @@ int DALI_EXPORT_API main(int argc, char** argv) demo.AddExample(Example("metaball-refrac.example", DALI_DEMO_STR_TITLE_METABALL_REFRAC)); demo.AddExample(Example("motion-blur.example", DALI_DEMO_STR_TITLE_MOTION_BLUR)); demo.AddExample(Example("page-turn-view.example", DALI_DEMO_STR_TITLE_PAGE_TURN)); + demo.AddExample(Example("particles.example", DALI_DEMO_STR_TITLE_PARTICLES)); demo.AddExample(Example("reflection-demo.example", DALI_DEMO_STR_TITLE_REFLECTION)); demo.AddExample(Example("refraction-effect.example", DALI_DEMO_STR_TITLE_REFRACTION)); demo.AddExample(Example("renderer-stencil.example", DALI_DEMO_STR_TITLE_RENDERER_STENCIL)); diff --git a/examples/motion-blur/motion-blur-example.cpp b/examples/motion-blur/motion-blur-example.cpp index 174d390..e52e2c8 100644 --- a/examples/motion-blur/motion-blur-example.cpp +++ b/examples/motion-blur/motion-blur-example.cpp @@ -183,10 +183,10 @@ public: mTapGestureDetector.DetectedSignal().Connect(this, &MotionBlurExampleApp::OnTap); Dali::Window winHandle = app.GetWindow(); - winHandle.AddAvailableOrientation(Dali::Window::PORTRAIT); - winHandle.AddAvailableOrientation(Dali::Window::LANDSCAPE); - winHandle.AddAvailableOrientation(Dali::Window::PORTRAIT_INVERSE); - winHandle.AddAvailableOrientation(Dali::Window::LANDSCAPE_INVERSE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::PORTRAIT); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::LANDSCAPE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::PORTRAIT_INVERSE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::LANDSCAPE_INVERSE); winHandle.ResizeSignal().Connect(this, &MotionBlurExampleApp::OnWindowResized); // set initial orientation diff --git a/examples/motion-stretch/motion-stretch-example.cpp b/examples/motion-stretch/motion-stretch-example.cpp index 7b815fa..dd55e34 100644 --- a/examples/motion-stretch/motion-stretch-example.cpp +++ b/examples/motion-stretch/motion-stretch-example.cpp @@ -162,10 +162,10 @@ public: // set initial orientation Dali::Window winHandle = app.GetWindow(); - winHandle.AddAvailableOrientation(Dali::Window::PORTRAIT); - winHandle.AddAvailableOrientation(Dali::Window::LANDSCAPE); - winHandle.AddAvailableOrientation(Dali::Window::PORTRAIT_INVERSE); - winHandle.AddAvailableOrientation(Dali::Window::LANDSCAPE_INVERSE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::PORTRAIT); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::LANDSCAPE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::PORTRAIT_INVERSE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::LANDSCAPE_INVERSE); winHandle.ResizeSignal().Connect(this, &MotionStretchExampleApp::OnWindowResized); // set initial orientation diff --git a/examples/page-turn-view/page-turn-view-example.cpp b/examples/page-turn-view/page-turn-view-example.cpp index dc3315a..b7641fc 100644 --- a/examples/page-turn-view/page-turn-view-example.cpp +++ b/examples/page-turn-view/page-turn-view-example.cpp @@ -199,10 +199,10 @@ void PageTurnExample::OnInit(Application& app) Window window = app.GetWindow(); window.KeyEventSignal().Connect(this, &PageTurnExample::OnKeyEvent); - window.AddAvailableOrientation(Window::PORTRAIT); - window.AddAvailableOrientation(Window::LANDSCAPE); - window.AddAvailableOrientation(Window::PORTRAIT_INVERSE); - window.AddAvailableOrientation(Window::LANDSCAPE_INVERSE); + window.AddAvailableOrientation(WindowOrientation::PORTRAIT); + window.AddAvailableOrientation(WindowOrientation::LANDSCAPE); + window.AddAvailableOrientation(WindowOrientation::PORTRAIT_INVERSE); + window.AddAvailableOrientation(WindowOrientation::LANDSCAPE_INVERSE); window.ResizeSignal().Connect(this, &PageTurnExample::OnWindowResized); Window::WindowSize size = window.GetSize(); diff --git a/examples/particles/float-rand.h b/examples/particles/float-rand.h new file mode 100644 index 0000000..7589a8e --- /dev/null +++ b/examples/particles/float-rand.h @@ -0,0 +1,38 @@ +#ifndef PARTICLES_FLOAT_RAND_H_ +#define PARTICLES_FLOAT_RAND_H_ +/* + * Copyright (c) 2020 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 + +struct FloatRand +{ + std::random_device mDevice; + std::mt19937 mMersenneTwister; + std::uniform_real_distribution mDistribution; + + FloatRand() + : mMersenneTwister(mDevice()), + mDistribution(0., 1.) + {} + + float operator()() + { + return mDistribution(mMersenneTwister); + } +}; + +#endif //PARTICLES_FLOAT_RAND_H_ \ No newline at end of file diff --git a/examples/particles/particle-field.h b/examples/particles/particle-field.h new file mode 100644 index 0000000..a629572 --- /dev/null +++ b/examples/particles/particle-field.h @@ -0,0 +1,132 @@ +#ifndef PARTICLES_PARTICLE_FIELD_H_ +#define PARTICLES_PARTICLE_FIELD_H_ +/* + * Copyright (c) 2020 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 "utils.h" +#include "float-rand.h" +#include "dali/public-api/math/vector2.h" +#include "dali/public-api/math/vector3.h" +#include "dali/public-api/math/vector4.h" +#include "dali/public-api/rendering/geometry.h" +#include + +struct ParticleField +{ + float mSize; + Dali::Vector3 mBoxSize; + Dali::Vector3 mParticlesPerAxis; + float mSizeVariance; + float mNoiseAmount; // affects color, motion (phase), twinkle (frequency, phase, size, opacity), + float mDisperse; + float mMotionScale; + float mMotionCycleLength; // seconds + float mTwinkleFrequency; // per motion cycle + float mTwinkleSizeScale; + float mTwinkleOpacityWeight; + + Dali::Vector3 GetParticlesPerAxisSafe() const + { + using namespace Dali; + return Vector3(std::max(1.f, FastFloor(mParticlesPerAxis.x)), + std::max(1.f, FastFloor(mParticlesPerAxis.y)), + std::max(1.f, FastFloor(mParticlesPerAxis.z))); + } + + Dali::Geometry MakeGeometry() const + { + using namespace Dali; + FloatRand frandPath; + FloatRand frandSeed; + FloatRand frandPos; + FloatRand frandDisperse; + FloatRand frandSize; + + struct Vertex + { + Vector3 aPosition; + float aSeed; + Vector4 aPath; + Vector2 aSubPosition; + float aSize; + }; + + const int numPatternVertices = 6; + Vector2 vertexPattern[numPatternVertices] = { + Vector2(-1.f, 1.f), + Vector2(-1.f, -1.f), + Vector2(1.f, 1.f), + Vector2(1.f, 1.f), + Vector2(-1.f, -1.f), + Vector2(1.f, -1.f), + }; + + Vector3 particlesPerAxis = GetParticlesPerAxisSafe(); + auto numParticles = particlesPerAxis.x * particlesPerAxis.y * particlesPerAxis.z; + + std::vector vertices; + vertices.reserve(numParticles * numPatternVertices); + + Vector3 invBoxSize(1. / std::max(mBoxSize.x, 1.f), + 1. / std::max(mBoxSize.y, 1.f), + 1. / std::max(mBoxSize.z, 1.f)); + Vector3 spacing(mBoxSize.x / particlesPerAxis.x, + mBoxSize.y / particlesPerAxis.y, + mBoxSize.z / particlesPerAxis.z); + auto offset = (mBoxSize - spacing) * .5; + int nx = particlesPerAxis.x; + int ny = particlesPerAxis.y; + int nxy = nx * ny; + for (size_t i = 0; i < numParticles; ++i) + { + Vertex v; + v.aPosition = Vector3((i % nx) * spacing.x, (i / nx) % ny * spacing.y, (i / nxy) * spacing.z) - offset; + + Vector3 disperseDir(frandDisperse() - .5, frandDisperse() - .5, frandDisperse() - .5); + disperseDir.Normalize(); + + v.aPosition += disperseDir * (frandDisperse() * mDisperse); + v.aPosition *= invBoxSize; + + v.aSeed = frandSeed() * mNoiseAmount; + v.aPath = Vector4(frandPath() - .5, frandPath() - .5, frandPath() - .5, frandPath() - .5) * mMotionScale; + + const float size = mSize * ((1.f + (frandSize() - .5) * mSizeVariance) * .5f); + for (int j = 0; j < numPatternVertices; ++j) + { + v.aSubPosition = vertexPattern[j]; + v.aSize = size; + vertices.push_back(v); + } + } + + VertexBuffer vertexBuffer = VertexBuffer::New( Property::Map() + .Add( "aPosition", Property::VECTOR3 ) + .Add( "aSeed", Property::FLOAT ) + .Add( "aPath", Property::VECTOR4 ) + .Add( "aSubPosition", Property::VECTOR2 ) + .Add( "aSize", Property::FLOAT ) + ); + vertexBuffer.SetData( vertices.data(), vertices.size() ); + + Geometry geometry = Geometry::New(); + geometry.AddVertexBuffer( vertexBuffer ); + geometry.SetType( Geometry::TRIANGLES ); + return geometry; + } +}; + +#endif //PARTICLES_PARTICLE_FIELD_H_ diff --git a/examples/particles/particle-view.cpp b/examples/particles/particle-view.cpp new file mode 100644 index 0000000..4af6fdb --- /dev/null +++ b/examples/particles/particle-view.cpp @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2020 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-view.h" +#include "utils.h" +#include "dali/public-api/animation/constraints.h" + +//#define ENABLE_DEBUG_VOLUME + +#define USE_GLSL_VERSION(version) "#version " #version "\n" + +using namespace Dali; + +namespace +{ + +const uint32_t POPULATION_GRANULARITY = 128; + +///@brief Shader for billboarded particles, where the vertices of the particles +/// are supplied as vec3 position (particle position) + vec2 sub-position. +const char* const PARTICLES_VSH = USE_GLSL_VERSION(300 es) +DALI_COMPOSE_SHADER( + precision lowp float; + uniform mat4 uModelView; // DALi + uniform mat4 uProjection; // DALi + uniform vec3 uSize; // DALi + uniform vec4 uColor; // DALi + + uniform vec3 uSecondaryColor; + uniform vec2 uDepthRange; // x is zNear, y is 1.f / (zFar - zNear) + uniform float uTwinkleFrequency; + uniform float uTwinkleSizeScale; + uniform float uTwinkleOpacityWeight; + uniform float uTime; + uniform float uFocalLength; + uniform float uAperture; + uniform float uPopulation; + + struct Scatter + { + float radiusSqr; + float amount; + vec3 ray; + }; + + const int SCATTER_VARS = 6; // Must match ParticleView::mScatterProps' size. + uniform Scatter uScatter[SCATTER_VARS]; + + const int POPULATION_GRANULARITY = 128; + uniform float uOrderLookUp[POPULATION_GRANULARITY]; + + in vec3 aPosition; + in float aSeed; + in vec4 aPath; + in vec2 aSubPosition; + in float aSize; + + flat out float vDepth; + flat out float vFocalDistance; + out vec2 vUvUnit; + flat out float vOpacity; + flat out vec3 vColor; // ignore alpha + + float bezier(vec3 control, float alpha) + { + return mix(mix(control.x, control.y, alpha), mix(control.y, control.z, alpha), alpha); + } + + void main() { + // Get random order from the look-up table, based on particle ID. + int particleId = gl_VertexID / 6; + float order = uOrderLookUp[particleId & (POPULATION_GRANULARITY - 1)]; + + // Get twinkle scalar + float twinkle = sin(uTime * floor(uTwinkleFrequency * aSeed) + fract(aSeed * 1.17137)); + + // Add Motion + float s = sin(uTime + aSeed) * .5f + .5f; // different phase for all + // NOTE: you'd think that taking the bezier() calls apart would save 4 mix() calls, since + // the mix()es (of xy / yz / zw / wx) are all calculated twice. It turns out that the MALI + // compiler is already doing this; leaving it as is for readability. + float bx0 = bezier(aPath.xyz, s); + float bx1 = bezier(aPath.zwx, s); + float by0 = bezier(aPath.yzw, s); + float by1 = bezier(aPath.wxy, s); + vec3 motion = vec3(mix(bx0, bx1, s), mix(by0, by1, s), 0.f); + + // Model to view position + vec3 position3 = aPosition * uSize + motion; + + vec4 position = uModelView * vec4(position3, 1.f); + + // Add scatter - calculated in view space, using view ray + vec3 normalizedPos = position.xyz / uSize; + for (int i = 0; i < SCATTER_VARS; ++i) + { + vec2 scatterDist = (normalizedPos - uScatter[i].ray * dot(uScatter[i].ray, normalizedPos)).xy; + + // NOTE: replacing the division with a multiplication (by inverse) oddly results in more instructions (MALI). + float scatter = max(0.f, uScatter[i].radiusSqr - dot(scatterDist, scatterDist)) * + uScatter[i].amount / aSize; + position.xy += scatter * normalize(scatterDist) * uSize.xy; + } + + // Calculate normalised depth and distance from focal plane + float depth = (position.z - uDepthRange.x) * uDepthRange.y; + vDepth = depth; + + float focalDist = (uFocalLength - depth) * uAperture; + focalDist *= focalDist; + vFocalDistance = max(focalDist, 1e-6f); // NOTE: was clamp(..., 1.f); side effect: out of focus particles get squashed at higher aperture values. + + // Calculate expiring scale - for size and opacity. + float expiringScale = smoothstep(order + 1.f, order, uPopulation); + + // Calculate billboard position and size + vec2 subPosition = aSubPosition * aSize * + (1.f + twinkle * aSeed * uTwinkleSizeScale) * + expiringScale; + + // Insist on hacking the size? Do it here... + float sizeHack = depth + .5f; + // NOTE: sizeHack *= sizeHack looked slightly better. + subPosition *= sizeHack; + + vec3 subPositionView = vec3(subPosition, 0.); + + // Add billboards to view position. + position += vec4(subPositionView, 0.f); + + // subPosition doubles as normalized (-1..1) UV. + vUvUnit = aSubPosition; + + // Vary opacity (actor alpha) by time as well as expiring scale. + vOpacity = uColor.a * expiringScale * + (1.0f + aSeed + twinkle * uTwinkleOpacityWeight) / (2.0f + uTwinkleOpacityWeight); + + // Randomize RGB using seed. + vec3 mixColor = vec3(fract(aSeed), fract(aSeed * 16.f), fract(aSeed * 256.f)); + vColor = mix(uColor.rgb, uSecondaryColor, mixColor); + + gl_Position = uProjection * position; + }); + +///@brief Fragment shader for particles, which simulates depth of field +/// using a combination of procedural texturing, alpha testing and alpha +/// blending. +const char* const PARTICLES_FSH = USE_GLSL_VERSION(300 es) +DALI_COMPOSE_SHADER( + precision lowp float; + uniform float uAlphaTestRefValue; + uniform vec2 uFadeRange; // near, far + in vec2 vUvUnit; + flat in float vDepth; + flat in float vFocalDistance; + flat in float vOpacity; + flat in vec3 vColor; + out vec4 oFragColor; + + const float REF_VALUE_THRESHOLD = 1. / 64.; + + void main() { + // Softened disc pattern from normalized UVs + float value = 1.f - dot(vUvUnit, vUvUnit); + + // Decrease area of particles 'in-focus'. + float refValue = (1.f - vFocalDistance) * .5f; + float threshold = REF_VALUE_THRESHOLD * (1.f + vDepth); + float alpha = pow(value, vFocalDistance) * smoothstep(refValue - threshold, refValue + threshold, value); + if (alpha < uAlphaTestRefValue) + { + discard; + } + + // Apply opacity + alpha *= vOpacity; + alpha *= alpha; + + // Fade particles out as they get close to the near and far clipping planes + alpha *= smoothstep(.0f, uFadeRange.x, vDepth) * smoothstep(1.f, uFadeRange.y, vDepth); + + oFragColor = vec4(vColor, alpha); + }); + +///@brief Shader for simple textured geometry. +const char* const SIMPLE_VSH = USE_GLSL_VERSION(300 es) +DALI_COMPOSE_SHADER( + precision mediump float; + uniform mat4 uMvpMatrix;//by DALi + uniform vec3 uSize; // by DALi + in vec3 aPosition; + void main() { + gl_Position = uMvpMatrix * vec4(aPosition * uSize, 1.f); + }); + +///@brief Shader for an unlit, unfogged, textured mesh. +const char* const SIMPLE_FSH = USE_GLSL_VERSION(300 es) +DALI_COMPOSE_SHADER( + precision mediump float; + uniform vec4 uColor; + out vec4 oFragColor; + + void main() { + oFragColor = uColor; + }); + + +uint32_t GetSkipValue(uint32_t count, uint32_t prime) +{ + uint32_t skip = 0; + do + { + skip = (rand() % prime) * count * count + (rand() % prime) * count + (rand() % prime); + } + while (skip % prime == 0); + return skip; +} + +} + +ParticleView::ParticleView(const ParticleField& field, Dali::Actor world, Dali::CameraActor camera, + Dali::Geometry particleGeom) +: mWorld(world), + mParticleBoxSize(field.mBoxSize) +{ + if (!particleGeom) + { + // create particles + particleGeom = field.MakeGeometry(); + } + + // create shader + Shader particleShader = Shader::New(PARTICLES_VSH, PARTICLES_FSH, Shader::Hint::MODIFIES_GEOMETRY); + + float zNear = camera.GetNearClippingPlane(); + float zFar = camera.GetFarClippingPlane(); + const Vector2 depthRange(zNear, 1.f / (zFar - zNear)); + particleShader.RegisterProperty("uDepthRange", depthRange); + + particleShader.RegisterProperty("uTwinkleFrequency", field.mTwinkleFrequency); + particleShader.RegisterProperty("uTwinkleSizeScale", field.mTwinkleSizeScale); + particleShader.RegisterProperty("uTwinkleOpacityWeight", field.mTwinkleOpacityWeight); + + mPropPopulation = particleShader.RegisterProperty("uPopulation", 1.f); + mPropFocalLength = particleShader.RegisterProperty("uFocalLength", .5f); + mPropAperture = particleShader.RegisterProperty("uAperture", 8.f); + mPropAlphaTestRefValue = particleShader.RegisterProperty("uAlphaTestRefValue", 0.f); + mPropFadeRange = particleShader.RegisterProperty("uFadeRange", Vector2(0.f, 1.f)); + + // scatter variables + char nameBuffer[64]; + char* writep = nameBuffer + sprintf(nameBuffer, "uScatter["); + for (uint32_t i = 0; i < std::extent::value; ++i) + { + char* writep2 = writep + sprintf(writep, "%d].", i); + + sprintf(writep2, "radiusSqr"); + mScatterProps[i].mPropRadius = particleShader.RegisterProperty(nameBuffer, 0.f); + + sprintf(writep2, "amount"); + mScatterProps[i].mPropAmount = particleShader.RegisterProperty(nameBuffer, 0.f); + + sprintf(writep2, "ray"); + mScatterProps[i].mPropRay = particleShader.RegisterProperty(nameBuffer, Vector3::ZERO); + } + + // Create a look-up table for pseudo-random traversal of particles. + // Our particle mesh is sorted in Z; changing the population should remove + // particles "randomly", not from one end. + // Algorithm described in Mike McShaffry & al: Game Coding Complete. + const uint32_t prime = 131; // next prime after POPULATION_GRANULARITY + const uint32_t skip = GetSkipValue(POPULATION_GRANULARITY, prime); + uint32_t next = 0; + + writep = nameBuffer + sprintf(nameBuffer, "uOrderLookUp["); + for (uint32_t i = 0; i < POPULATION_GRANULARITY; ++i) + { + do { + next += skip; + next %= prime; + } + while (next == 0 || next > POPULATION_GRANULARITY); + + sprintf(writep, "%d]", i); + particleShader.RegisterProperty(nameBuffer, float(next - 1)); + } + + // create animation for time in shader + auto propTime = particleShader.RegisterProperty("uTime", 0.f); + + Animation animTime = Animation::New(field.mMotionCycleLength); + animTime.AnimateTo(Property(particleShader, propTime), static_cast(M_PI * 2.f)); + animTime.SetLoopCount(0); + animTime.Play(); + + mParticleShader = particleShader; + + auto renderer = CreateRenderer(TextureSet::New(), particleGeom, particleShader, OPTION_BLEND); + auto masterParticles = CreateActor(); + masterParticles.SetProperty(Actor::Property::SIZE, field.mBoxSize); + masterParticles.SetProperty(Actor::Property::VISIBLE, true); + masterParticles.AddRenderer(renderer); + + mPropSecondaryColor = masterParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS); + +#ifdef ENABLE_DEBUG_VOLUME + Geometry cubeGeom = CreateCuboidWireframeGeometry(); + renderer = CreateRenderer(renderer.GetTextures(), cubeGeom, Shader::New(SIMPLE_VSH, SIMPLE_FSH)); + masterParticles.AddRenderer(renderer); +#endif + + world.Add(masterParticles); + mMasterParticles = masterParticles; +} + +ParticleView::~ParticleView() +{ + UnparentAndReset(mMasterParticles); + UnparentAndReset(mSlaveParticles); + + for (auto anim: { mAngularAnim, mLinearAnim }) + { + if (anim) + { + anim.Stop(); + anim.Reset(); + } + } + + for (auto& s: mScatterProps) + { + auto& anim = s.mAnim; + if (anim) + { + anim.Stop(); + anim.Reset(); + } + } +} + +void ParticleView::SetColorRange(const ColorRange& range) +{ + mMasterParticles.SetProperty(Actor::Property::COLOR_RED, range.rgb0.r); + mMasterParticles.SetProperty(Actor::Property::COLOR_GREEN, range.rgb0.g); + mMasterParticles.SetProperty(Actor::Property::COLOR_BLUE, range.rgb0.b); + + mMasterParticles.SetProperty(mPropSecondaryColor, range.rgb1); +} + +void ParticleView::SetPopulation(float percentage) +{ + percentage = 1.f - std::min(1.f, std::max(0.f, percentage)); + mParticleShader.SetProperty(mPropPopulation, POPULATION_GRANULARITY * percentage); +} + +void ParticleView::SetFocalLength(float f) +{ + mParticleShader.SetProperty(mPropFocalLength, f); +} + +void ParticleView::SetAperture(float a) +{ + mParticleShader.SetProperty(mPropAperture, a); +} + +void ParticleView::SetAlphaTestRefValue(float rv) +{ + mParticleShader.SetProperty(mPropAlphaTestRefValue, rv); +} + +void ParticleView::SetFadeRange(float near, float far) +{ + mParticleShader.SetProperty(mPropFadeRange, Vector2(near, far)); +} + +void ParticleView::SetAngularVelocity(float v) +{ + if (mAngularAnim) + { + mAngularAnim.Stop(); + mAngularAnim.Clear(); + mAngularAnim.Reset(); + } + + if (v * v > .0f) + { + float sign = Sign(v); + auto anim = Animation::New(std::abs(2. * M_PI / v)); + anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION), + Quaternion(Radian(Degree(120. * sign)), Vector3::ZAXIS), TimePeriod(0., anim.GetDuration() / 3.)); + anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION), + Quaternion(Radian(Degree(240. * sign)), Vector3::ZAXIS), TimePeriod(anim.GetDuration() / 3., anim.GetDuration() / 3.)); + anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION), + Quaternion(Radian(Degree(360. * sign)), Vector3::ZAXIS), TimePeriod(2. * anim.GetDuration() / 3., anim.GetDuration() / 3.)); + anim.SetLoopCount(0); + anim.Play(); + + mAngularAnim = anim; + } +} + +void ParticleView::SetLinearVelocity(float v) +{ + if (mLinearAnim) + { + mLinearAnim.Stop(); + mLinearAnim.Clear(); + mLinearAnim.Reset(); + } + UnparentAndReset(mSlaveParticles); + + if (v * v > .0f) + { + float sign = Sign(v); + float directedSize = sign * mParticleBoxSize.z; + + Actor slaveParticles = CloneActor(mMasterParticles); + Vector3 position = mMasterParticles.GetCurrentProperty(Actor::Property::POSITION).Get(); + slaveParticles.SetProperty(Actor::Property::POSITION, position + Vector3(0., 0., directedSize)); + + auto propSecondaryColor = slaveParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS); + + Actor world = mWorld.GetHandle(); + world.Add(slaveParticles); + + if (sign < 0.) // fix draw order + { + world.Remove(mMasterParticles); + world.Add(mMasterParticles); + } + + Constraint constraint = Constraint::New(slaveParticles, Actor::Property::COLOR, + EqualToConstraint()); + constraint.AddSource(Source(mMasterParticles, Actor::Property::COLOR)); + constraint.Apply(); + + constraint = Constraint::New(slaveParticles, propSecondaryColor, + EqualToConstraint()); + constraint.AddSource(Source(mMasterParticles, mPropSecondaryColor)); + constraint.Apply(); + + constraint = Constraint::New(slaveParticles, Actor::Property::ORIENTATION, + EqualToConstraint()); + constraint.AddSource(Source(mMasterParticles, Actor::Property::ORIENTATION)); + constraint.Apply(); + + auto anim = Animation::New(std::abs(directedSize / v)); + anim.AnimateTo(Property(mMasterParticles, Actor::Property::POSITION_Z), position.z - directedSize); + anim.AnimateTo(Property(slaveParticles, Actor::Property::POSITION_Z), position.z); + anim.SetLoopCount(0); + anim.Play(); + + mLinearAnim = anim; + mSlaveParticles = slaveParticles; + } +} + +void ParticleView::Scatter(float radius, float amount, float durationOut, float durationIn) +{ + mActiveScatter = (mActiveScatter + 1) % std::extent::value; + + auto& scatter = mScatterProps[mActiveScatter]; + if (scatter.mAnim) + { + scatter.mAnim.Stop(); + } + + radius /= mParticleBoxSize.y; + radius *= radius; + mParticleShader.SetProperty(scatter.mPropRadius, radius); + + Animation anim = Animation::New(durationOut + durationIn); + auto scatterAmount = Property(mParticleShader, scatter.mPropAmount); + anim.AnimateTo(scatterAmount, amount, AlphaFunction::EASE_OUT, + TimePeriod(0.f, durationOut)); + anim.AnimateTo(scatterAmount, 0.f, AlphaFunction::EASE_IN_OUT_SINE, + TimePeriod(durationOut, durationIn)); + anim.Play(); + + scatter.mAnim = anim; +} + +void ParticleView::SetScatterRay(Dali::Vector3 rayDir) +{ + auto& scatter = mScatterProps[mActiveScatter]; + mParticleShader.SetProperty(scatter.mPropRay, rayDir);; +} + +void ParticleView::Fade(float duration, float target, AlphaFunction alphaFn, + std::function onFinished) +{ + if (mFadeAnim) + { + mFadeAnim.Stop(); + } + + Animation anim = Animation::New(duration); + anim.AnimateTo(Property(mMasterParticles, Actor::Property::COLOR_ALPHA), target, alphaFn); + if (mSlaveParticles) + { + anim.AnimateTo(Property(mSlaveParticles, Actor::Property::COLOR_ALPHA), target, alphaFn); + } + + if (onFinished) + { + anim.FinishedSignal().Connect(this, onFinished); + } + anim.Play(); + + mFadeAnim = anim; +} + +void ParticleView::Fade(float duration, float target, float from, AlphaFunction alphaFn, + std::function onFinished) +{ + mMasterParticles.SetProperty(Actor::Property::COLOR_ALPHA, from); + if (mSlaveParticles) + { + mSlaveParticles.SetProperty(Actor::Property::COLOR_ALPHA, from); + } + + Fade(duration, target, alphaFn, onFinished); +} diff --git a/examples/particles/particle-view.h b/examples/particles/particle-view.h new file mode 100644 index 0000000..099f568 --- /dev/null +++ b/examples/particles/particle-view.h @@ -0,0 +1,108 @@ +#ifndef PARTICLES_PARTICLE_VIEW_H_ +#define PARTICLES_PARTICLE_VIEW_H_ +/* + * Copyright (c) 2020 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-field.h" +#include "dali/public-api/actors/camera-actor.h" +#include "dali/public-api/animation/animation.h" +#include "dali/public-api/object/weak-handle.h" +#include "dali/public-api/rendering/shader.h" +#include +#include + +struct ColorRange +{ + Dali::Vector3 rgb0; + Dali::Vector3 rgb1; +}; + +class ParticleView: public Dali::ConnectionTracker +{ +public: + ParticleView(const ParticleField& field, Dali::Actor world, Dali::CameraActor camera, + Dali::Geometry particleGeom = Dali::Geometry()); + ~ParticleView(); + + void SetColorRange(const ColorRange& range); + + void SetPopulation(float percentage); + void SetFocalLength(float f); + void SetAperture(float a); + void SetAlphaTestRefValue(float rv); + void SetFadeRange(float near, float far); + void SetAngularVelocity(float v); + void SetLinearVelocity(float v); + + ///@brief Starts a scatter & regroup animation, cancelling any previously played animation + /// of the same kind. Bigger particles, and those further away from the Z axis are affected + /// less. + ///@param radius the normalised radius, within which particles are affected. + ///@param amount the amount of displacement applied to particles at the peak of the animation. + ///@param durationOut the duration of scattering, in seconds. + ///@param durationIn the duration of regrouping, in seconds. + void Scatter(float radius, float amount, float durationOut, float durationIn); + + void SetScatterRay(Dali::Vector3 rayDir); + + ///@brief Starts an animation to change the opacity of the particles to @a target. + ///@param duration Number of seconds to complete transition in. + ///@param target Target opacity in the 0..1.f range. + void Fade(float duration, float target, Dali::AlphaFunction alphaFn = Dali::AlphaFunction::DEFAULT, + std::function onFinished = nullptr); + + ///@brief Starts an animation to change the opacity of the particles to @a target. + ///@param duration Number of seconds to complete transition in. + ///@param target Target opacity in the 0..1.f range. + ///@param from The value to set the opacity to prior to the animation. + void Fade(float duration, float target, float from, Dali::AlphaFunction alphaFn = Dali::AlphaFunction::DEFAULT, + std::function onFinished = nullptr); + +private: // DATA + struct ScatterProps + { + Dali::Property::Index mPropRadius; + Dali::Property::Index mPropAmount; + Dali::Property::Index mPropRay; + + Dali::Animation mAnim; + }; + + Dali::WeakHandle mWorld; + Dali::Vector3 mParticleBoxSize; + + Dali::Shader mParticleShader; + Dali::Property::Index mPropPopulation; + Dali::Property::Index mPropFocalLength; + Dali::Property::Index mPropAperture; + Dali::Property::Index mPropAlphaTestRefValue; + Dali::Property::Index mPropFadeRange; + + ScatterProps mScatterProps[6]; + int mActiveScatter = 0; + + Dali::Actor mMasterParticles; + Dali::Property::Index mPropSecondaryColor; + + Dali::Actor mSlaveParticles; + + Dali::Animation mAngularAnim; + Dali::Animation mLinearAnim; + Dali::Animation mFadeAnim; +}; + +#endif //PARTICLES_PARTICLE_VIEW_H_ diff --git a/examples/particles/particles-example.cpp b/examples/particles/particles-example.cpp new file mode 100644 index 0000000..22dd815 --- /dev/null +++ b/examples/particles/particles-example.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2018 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. + * + */ + +// INTERNAL INCLUDES +#include "particle-view.h" +#include "float-rand.h" +#include "particle-field.h" +#include "utils.h" +#include "dali/devel-api/adaptor-framework/tilt-sensor.h" +#include "dali/public-api/adaptor-framework/application.h" +#include "dali/public-api/adaptor-framework/key.h" +#include "dali/public-api/actors/camera-actor.h" +#include "dali/public-api/actors/layer.h" +#include "dali/public-api/events/tap-gesture-detector.h" +#include "dali/public-api/events/pan-gesture-detector.h" +#include "dali/public-api/events/touch-event.h" +#include "dali/public-api/render-tasks/render-task-list.h" +#include "dali/public-api/render-tasks/render-task.h" +#include "dali/public-api/object/property-index-ranges.h" +#include +#include +#include +#include +#include +#include + +using namespace Dali; + +namespace +{ + +const float PARTICLE_ALPHA = .75; +const float ALPHA_TEST_REF_VALUE = .0625; + +const float NEAR_FADE = 0.04; // normalized depth +const float FAR_FADE = 0.8; // normalized depth + +const ParticleField PARTICLE_FIELD = { + 200.f, // particle size + Vector3(2500., 2500., 7800.), // box size - depth needs to be >= camera depth range + Vector3(6., 6., 12.), // number of particles + .333, // size variance + 1., // noise amount + 200., // disperse + 250., // motion scale + 15., // motion cycle length + 6., // twinkle frequency + .11, // twinkle size scale + .333 // twinkle opacity weight +}; + +const float WORLD_ANGULAR_VELOCITY = .08; // radians per seconds +const float WORLD_LINEAR_VELOCITY = -500; // units along z + +const float SCATTER_RADIUS = 450.0f; +const float SCATTER_AMOUNT = 180.0f; +const float SCATTER_DURATION_OUT = .8f; +const float SCATTER_DURATION_IN = 1.5f; + +const float FADE_DURATION = 1.2f; +const float FADEOUT_SPEED_MULTIPLIER = 4.f; // speed multiplier upon fading out. + +const float FOCAL_LENGTH = 0.5f; // normalized depth value where particles appear sharp. +const float APERTURE = 2.2f; // distance scale - the higher it is, the quicker the particles get blurred out moving away from the focal length. + +const ColorRange DEFAULT_COLOR_RANGE { Vector3(0., 48. / 255., 1.), Vector3(0., 216. / 255., 1.) }; + +const float TILT_SCALE = 0.2; +const float TILT_RANGE_DEGREES = 30.f; + +FloatRand sFloatRand; + +class TiltFilter +{ +public: + void Reset() + { + std::fill(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)); + } + + void Add(Dali::Vector2 tilt) + { + mTiltSamples[mIdxNextSample] = tilt; + mIdxNextSample = (mIdxNextSample + 1) % FILTER_SIZE; + } + + Dali::Vector2 Filter() const + { + return std::accumulate(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)) / FILTER_SIZE; + } + +private: + enum { FILTER_SIZE = 8u }; + + Dali::Vector2 mTiltSamples[FILTER_SIZE]; + size_t mIdxNextSample = 0; +}; + +class ParticlesExample : public ConnectionTracker +{ +public: + ParticlesExample( Application& app ) + : mApp( app ) + { + mApp.InitSignal().Connect(this, &ParticlesExample::OnInit); + mApp.TerminateSignal().Connect(this, &ParticlesExample::OnTerminate); + } + + ~ParticlesExample() = default; + +private: + Application& mApp; + + CameraActor mCamera; + + Actor mWorld; + Vector2 mAngularPosition; + ColorRange mColors; + + std::unique_ptr mParticles; + std::unique_ptr mExpiringParticles; + + TapGestureDetector mDoubleTapGesture; + + TiltSensor mTiltSensor; + TiltFilter mTiltFilter; + + PanGestureDetector mPanGesture; + + void OnInit( Application& application ) + { + Window window = application.GetWindow(); + auto rootLayer = window.GetRootLayer(); + rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::Behavior::LAYER_3D); + + window.KeyEventSignal().Connect( this, &ParticlesExample::OnKeyEvent ); + window.GetRootLayer().TouchedSignal().Connect( this, &ParticlesExample::OnTouched ); + + auto tiltSensor = TiltSensor::Get(); + if ( tiltSensor.Start() ) + { + // Get notifications when the device is tilted + tiltSensor.TiltedSignal().Connect( this, &ParticlesExample::OnTilted ); + } + else + { + mPanGesture = PanGestureDetector::New(); + mPanGesture.Attach(rootLayer); + mPanGesture.DetectedSignal().Connect(this, &ParticlesExample::OnPan); + } + + // Get camera + RenderTaskList tasks = window.GetRenderTaskList(); + RenderTask mainPass = tasks.GetTask(0); + CameraActor camera = mainPass.GetCameraActor(); + mCamera = camera; + + // Create world - particles and clock are added to it; this is what we apply tilt to. + auto world = CreateActor(); + world.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS); + window.Add(world); + mWorld = world; + + // Create particles + TriggerColorTransition(DEFAULT_COLOR_RANGE); + + // Setup double tap detector for color change + mDoubleTapGesture = TapGestureDetector::New(2); + mDoubleTapGesture.Attach(rootLayer); + mDoubleTapGesture.DetectedSignal().Connect(this, &ParticlesExample::OnDoubleTap); + } + + void OnTerminate(Application& app) + { + UnparentAndReset(mWorld); + + mDoubleTapGesture.Reset(); + mPanGesture.Reset(); + mTiltSensor.Reset(); + } + + void OnPause(Application& app) + { + mTiltSensor.Stop(); + } + + void OnResume(Application& app) + { + mTiltSensor.Start(); + } + + void OnKeyEvent(const KeyEvent& event) + { + if ( event.GetState() == KeyEvent::UP) // single keystrokes + { + if( IsKey( event, DALI_KEY_ESCAPE ) || IsKey( event, DALI_KEY_BACK ) ) + { + mApp.Quit(); + } + } + } + + bool OnTouched( Actor a, const TouchEvent& event ) + { + if (event.GetPointCount() > 0) + { + auto screenPos = event.GetScreenPosition(0); + switch (event.GetState(0)) + { + case PointState::STARTED: + { + mParticles->Scatter(SCATTER_RADIUS, SCATTER_AMOUNT, SCATTER_DURATION_OUT, SCATTER_DURATION_IN); + + Vector3 ray = GetViewRay(screenPos); + mParticles->SetScatterRay(ray); + } + break; + + default: + break; + } + } + + return false; + } + + void OnDoubleTap(Actor /*actor*/, const TapGesture& /*gesture*/) + { + if (!mExpiringParticles) + { + mColors.rgb0 = Vector3::ONE - mColors.rgb1; + mColors.rgb1 = FromHueSaturationLightness(Vector3(sFloatRand() * 360.f, sFloatRand() * .5f + .5f, sFloatRand() * .25 + .75f)); + + TriggerColorTransition(mColors); + } + } + + void OnPan(Actor actor, const PanGesture& gesture) + { + auto tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize()) * TILT_SCALE; + Quaternion q(Radian(-tilt.y), Radian(tilt.x), Radian(0.f)); + Quaternion q0 = mWorld.GetProperty(Actor::Property::ORIENTATION).Get(); + mWorld.SetProperty(Actor::Property::ORIENTATION, q * q0); + } + + void OnTilted( const TiltSensor& sensor) + { + mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll())); + Vector2 tilt = mTiltFilter.Filter() * TILT_RANGE_DEGREES; + Quaternion q(Radian(Degree(tilt.x)), Radian(Degree(tilt.y)), Radian(0.f)); + mWorld.SetProperty(Actor::Property::ORIENTATION, q); + } + + Vector3 GetViewRay(const Vector2& screenPos) + { + Vector2 screenSize = mApp.GetWindow().GetSize(); + Vector2 normScreenPos = (screenPos / screenSize) * 2.f - Vector2::ONE; + + const float fov = mCamera.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get(); + const float tanFov = std::tan(fov); + + const float zNear = mCamera.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get(); + const float hProj = zNear * tanFov; + + const float aspectRatio = mCamera.GetProperty(CameraActor::Property::ASPECT_RATIO).Get(); + const float wProj = hProj * aspectRatio; + + // Get camera orientation for view space ray casting. Assume: + // - this to be world space, i.e. no parent transforms; + // - no scaling; + Quaternion cameraOrientation = mCamera.GetProperty(Actor::Property::ORIENTATION).Get(); + + Matrix worldCamera; + worldCamera.SetTransformComponents(Vector3::ONE, cameraOrientation, Vector3::ZERO); + + float* data = worldCamera.AsFloat(); + Vector3 xWorldCamera(data[0], data[4], data[8]); + xWorldCamera *= wProj * normScreenPos.x / xWorldCamera.Length(); + + Vector3 yWorldCamera(data[1], data[5], data[9]); + yWorldCamera *= hProj * normScreenPos.y / yWorldCamera.Length(); + + Vector3 zWorldCamera(data[2], data[6], data[10]); + Vector3 ray = xWorldCamera + yWorldCamera + zWorldCamera * -zNear; + ray.Normalize(); + + return ray; + } + + void TriggerColorTransition(const ColorRange& range) + { + if (mParticles) + { + mExpiringParticles = std::move(mParticles); + + // NOTE: this will break the perfect looping, but we can get away with it because we're fading out. + mExpiringParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY * FADEOUT_SPEED_MULTIPLIER); + + auto& p = mExpiringParticles; + mExpiringParticles->Fade(FADE_DURATION, 0.f, AlphaFunction::EASE_IN, [&p](Animation&) { + p.reset(); + }); + } + + mParticles.reset(new ParticleView(PARTICLE_FIELD, mWorld, mCamera)); + mParticles->SetColorRange(range); + mParticles->SetFocalLength(FOCAL_LENGTH); + mParticles->SetAperture(APERTURE); + mParticles->SetAlphaTestRefValue(ALPHA_TEST_REF_VALUE); + mParticles->SetFadeRange(NEAR_FADE, FAR_FADE); + mParticles->SetAngularVelocity(WORLD_ANGULAR_VELOCITY); + mParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY); + mParticles->Fade(FADE_DURATION, PARTICLE_ALPHA, 0.f, AlphaFunction::EASE_OUT); + } +}; + +} // nonamespace + +int DALI_EXPORT_API main( int argc, char **argv ) +{ + Application application = Application::New( &argc, &argv, DEMO_THEME_PATH ); + ParticlesExample example( application); + application.MainLoop(); + return 0; +} diff --git a/examples/particles/utils.cpp b/examples/particles/utils.cpp new file mode 100644 index 0000000..7f7432e --- /dev/null +++ b/examples/particles/utils.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2020 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 "utils.h" + +using namespace Dali; + +Vector3 ToHueSaturationLightness(Vector3 rgb) +{ + float min = std::min(rgb.r, std::min(rgb.g, rgb.b)); + float max = std::max(rgb.r, std::max(rgb.g, rgb.b)); + + Vector3 hsl(max - min, 0.f, (max + min) * .5f); + if (hsl.x * hsl.x > .0f) + { + hsl.y = hsl.x / max; + if (max == rgb.r) + { + hsl.x = (rgb.g - rgb.b) / hsl.x; + } + else if(max == rgb.g) + { + hsl.x = 2.f + (rgb.b - rgb.r) / hsl.x; + } + else + { + hsl.x = 4.f + (rgb.r - rgb.g) / hsl.x; + } + hsl.x *= 60.f; + if (hsl.x < 0.f) + { + hsl.x += 360.f; + } + } + else + { + hsl.y = 0.f; + } + + return hsl; +} + +Vector3 FromHueSaturationLightness(Vector3 hsl) +{ + Vector3 rgb; + if (hsl.y * hsl.y > 0.f) + { + if(hsl.x >= 360.f) + { + hsl.x -= 360.f; + } + hsl.x /= 60.f; + + int i = FastFloor(hsl.x); + float ff = hsl.x - i; + float p = hsl.z * (1.0 - hsl.y); + float q = hsl.z * (1.0 - (hsl.y * ff)); + float t = hsl.z * (1.0 - (hsl.y * (1.f - ff))); + + switch (i) + { + case 0: + rgb.r = hsl.z; + rgb.g = t; + rgb.b = p; + break; + + case 1: + rgb.r = q; + rgb.g = hsl.z; + rgb.b = p; + break; + + case 2: + rgb.r = p; + rgb.g = hsl.z; + rgb.b = t; + break; + + case 3: + rgb.r = p; + rgb.g = q; + rgb.b = hsl.z; + break; + + case 4: + rgb.r = t; + rgb.g = p; + rgb.b = hsl.z; + break; + + case 5: + default: + rgb.r = hsl.z; + rgb.g = p; + rgb.b = q; + break; + } + } + else + { + rgb = Vector3::ONE * hsl.z; + } + + return rgb; +} + +Geometry CreateCuboidWireframeGeometry() +{ +// +// 2---3 +// |- |- +// | 6---7 +// | | | | +// 0-|-1 | +// -| -| +// 4---5 +// + Vector3 vertexData[] = { + Vector3(-.5, -.5, -.5), + Vector3( .5, -.5, -.5), + Vector3(-.5, .5, -.5), + Vector3( .5, .5, -.5), + Vector3(-.5, -.5, .5), + Vector3( .5, -.5, .5), + Vector3(-.5, .5, .5), + Vector3( .5, .5, .5), + }; + + uint16_t indices[] = { + 0, 1, 1, 3, 3, 2, 2, 0, + 0, 4, 1, 5, 3, 7, 2, 6, + 4, 5, 5, 7, 7, 6, 6, 4 + }; + VertexBuffer vertexBuffer = VertexBuffer::New( Property::Map() + .Add( "aPosition", Property::VECTOR3)); + vertexBuffer.SetData(vertexData, std::extent::value ); + + Geometry geometry = Geometry::New(); + geometry.AddVertexBuffer( vertexBuffer ); + geometry.SetIndexBuffer( indices, std::extent::value ); + geometry.SetType(Geometry::LINES); + return geometry; +} + +Renderer CreateRenderer(TextureSet textures, Geometry geometry, Shader shader, uint32_t options) +{ + Renderer renderer = Renderer::New(geometry, shader); + renderer.SetProperty(Renderer::Property::BLEND_MODE, + (options & OPTION_BLEND) ? BlendMode::ON : BlendMode::OFF); + renderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, + (options & OPTION_DEPTH_TEST) ? DepthTestMode::ON : DepthTestMode::OFF); + renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE, + (options & OPTION_DEPTH_WRITE) ? DepthWriteMode::ON : DepthWriteMode::OFF); + renderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK); + + if (!textures) + { + textures = TextureSet::New(); + } + + renderer.SetTextures(textures); + return renderer; +} + +void CenterActor(Actor actor) +{ + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER ); + actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER ); +} + +Actor CreateActor() +{ + auto actor = Actor::New(); + CenterActor(actor); + return actor; +} + +Renderer CloneRenderer(Renderer original) +{ + Geometry geom = original.GetGeometry(); + Shader shader = original.GetShader(); + Renderer clone = Renderer::New(geom, shader); + + // Copy properties. + Property::IndexContainer indices; + original.GetPropertyIndices(indices); + + for (auto& i: indices) + { + auto actualIndex = PropertyRanges::DEFAULT_RENDERER_PROPERTY_START_INDEX + i; + clone.SetProperty(actualIndex, original.GetProperty(actualIndex)); + } + + // Copy texture references (and create TextureSet, if there's any textures). + TextureSet ts = original.GetTextures(); + clone.SetTextures(ts); + + return clone; +} + +Actor CloneActor(Actor original) +{ + using namespace Dali; + + auto clone = Actor::New(); + clone.SetProperty(Actor::Property::NAME, original.GetProperty(Actor::Property::NAME)); + + // Copy properties. + // Don't copy every single one of them. + // Definitely don't copy resize policy related things, which will internally enable + // relayout, which in turn will result in losing the ability to set Z size. + for (auto i : { + Actor::Property::PARENT_ORIGIN, + Actor::Property::ANCHOR_POINT, + Actor::Property::SIZE, + Actor::Property::POSITION, + Actor::Property::ORIENTATION, + Actor::Property::SCALE, + Actor::Property::VISIBLE, + Actor::Property::COLOR, + Actor::Property::NAME, + }) + { + clone.SetProperty(i, original.GetProperty(i)); + } + + // Clone renderers. + for(unsigned int i = 0; i < original.GetRendererCount(); ++i) + { + auto rClone = CloneRenderer(original.GetRendererAt(i)); + clone.AddRenderer(rClone); + } + + // Recurse into children. + for(unsigned int i = 0; i < original.GetChildCount(); ++i) + { + Actor newChild = CloneActor(original.GetChildAt(i)); + clone.Add(newChild); + } + + return clone; +} diff --git a/examples/particles/utils.h b/examples/particles/utils.h new file mode 100644 index 0000000..10dee0d --- /dev/null +++ b/examples/particles/utils.h @@ -0,0 +1,86 @@ +#ifndef PARTICLES_UTILS_H_ +#define PARTICLES_UTILS_H_ +/* + * Copyright (c) 2020 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 "dali/public-api/actors/actor.h" +#include "dali/public-api/actors/actor.h" +#include "dali/public-api/rendering/geometry.h" +#include "dali/public-api/rendering/renderer.h" +#include "dali/public-api/rendering/shader.h" +#include "dali/public-api/rendering/texture.h" +#include "dali/public-api/math/vector3.h" +#include + +// +// Maths +// +inline +float FastFloor(float x) +{ + return static_cast(x) - static_cast(x < 0); +} + +///@brief Converts RGB values (in the 0..1 range) to HSL, where hue is in degrees, +/// in the 0..360 range, and saturation and lightness are in the 0..1 range. +Dali::Vector3 ToHueSaturationLightness(Dali::Vector3 rgb); + +///@brief Converts HSL values, where hue is in degrees, in the 0..360 range, and +/// saturation and lightness are in 0..1 to RGB (in the 0..1 range) +Dali::Vector3 FromHueSaturationLightness(Dali::Vector3 hsl); + +// +// Dali entities +// +///@brief Creates geometry for a unit cube wireframe (i.e. vertex positions between +/// -.5 and .5).. +Dali::Geometry CreateCuboidWireframeGeometry(); + +enum RendererOptions +{ + OPTION_NONE = 0x0, + OPTION_BLEND = 0x01, + OPTION_DEPTH_TEST = 0x02, + OPTION_DEPTH_WRITE = 0x04 +}; + +///@brief Creates a renderer with the given @a textures set, @a geometry, @a shader +/// and @a options from above. +///@note Back face culling is on. +///@note If textures is not a valid handle, an empty texture set will be created. +Dali::Renderer CreateRenderer(Dali::TextureSet textures, Dali::Geometry geometry, + Dali::Shader shader, uint32_t options = OPTION_NONE); + +///@brief Sets @a actor's anchor point and parent origin to center. +void CenterActor(Dali::Actor actor); + +///@brief Creates an empty and centered actor. +Dali::Actor CreateActor(); + +///@brief Creates a copy of @a original, sharing the same geometry and shader and +/// copying each properties. +///@note Breaks if @a original has any custom properties. TODO: fix. +Dali::Renderer CloneRenderer(Dali::Renderer original); + +///@brief Creates a copy of @a original, cloning each renderer, and a select set +/// of properties: parent origin, anchor point, size, position, orientation, scale, +/// visible, color and name. +///@note Does not copy resize policy related properties, as setting those, even if +/// default, will break the ability to specify a size for the actor in Z. +Dali::Actor CloneActor(Dali::Actor original); + +#endif //PARTICLES_UTILS_H_ diff --git a/resources/po/en_GB.po b/resources/po/en_GB.po index 6a3e68e..0cc2d8f 100755 --- a/resources/po/en_GB.po +++ b/resources/po/en_GB.po @@ -151,6 +151,9 @@ msgstr "Negotiate Size" msgid "DALI_DEMO_STR_TITLE_PAGE_TURN" msgstr "Page Turn" +msgid "DALI_DEMO_STR_TITLE_PARTICLES" +msgstr "Particles" + msgid "DALI_DEMO_STR_TITLE_PERF_SCROLL" msgstr "Scrolling Performance" diff --git a/resources/po/en_US.po b/resources/po/en_US.po index 4e7d858..23fcbec 100755 --- a/resources/po/en_US.po +++ b/resources/po/en_US.po @@ -154,6 +154,9 @@ msgstr "Negotiate Size" msgid "DALI_DEMO_STR_TITLE_PAGE_TURN" msgstr "Page Turn" +msgid "DALI_DEMO_STR_TITLE_PARTICLES" +msgstr "Particles" + msgid "DALI_DEMO_STR_TITLE_PERF_SCROLL" msgstr "Scrolling Performance" diff --git a/shared/dali-demo-strings.h b/shared/dali-demo-strings.h index 3569020..5fdff42 100644 --- a/shared/dali-demo-strings.h +++ b/shared/dali-demo-strings.h @@ -89,6 +89,7 @@ extern "C" #define DALI_DEMO_STR_TITLE_NATIVE_IMAGE_SOURCE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_NATIVE_IMAGE_SOURCE") #define DALI_DEMO_STR_TITLE_NEGOTIATE_SIZE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_NEGOTIATE_SIZE") #define DALI_DEMO_STR_TITLE_PAGE_TURN dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PAGE_TURN") +#define DALI_DEMO_STR_TITLE_PARTICLES dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PARTICLES") #define DALI_DEMO_STR_TITLE_PBR dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PBR") #define DALI_DEMO_STR_TITLE_PERF_SCROLL dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PERF_SCROLL") #define DALI_DEMO_STR_TITLE_POINT_MESH dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_POINT_MESH") @@ -191,6 +192,7 @@ extern "C" #define DALI_DEMO_STR_TITLE_NATIVE_IMAGE_SOURCE "Native Image Source" #define DALI_DEMO_STR_TITLE_NEGOTIATE_SIZE "Negotiate Size" #define DALI_DEMO_STR_TITLE_PAGE_TURN "Page Turn" +#define DALI_DEMO_STR_TITLE_PARTICLES "Particles" #define DALI_DEMO_STR_TITLE_PBR "PBR" #define DALI_DEMO_STR_TITLE_PERF_SCROLL "Scrolling Performance" #define DALI_DEMO_STR_TITLE_POINT_MESH "Point Mesh" diff --git a/shared/dali-table-view.cpp b/shared/dali-table-view.cpp index 3b3719a..d45e345 100755 --- a/shared/dali-table-view.cpp +++ b/shared/dali-table-view.cpp @@ -299,17 +299,17 @@ void DaliTableView::Initialize(Application& application) if(windowSize.GetWidth() <= windowSize.GetHeight()) { - winHandle.AddAvailableOrientation(Dali::Window::PORTRAIT); - winHandle.RemoveAvailableOrientation(Dali::Window::LANDSCAPE); - winHandle.AddAvailableOrientation(Dali::Window::PORTRAIT_INVERSE); - winHandle.RemoveAvailableOrientation(Dali::Window::LANDSCAPE_INVERSE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::PORTRAIT); + winHandle.RemoveAvailableOrientation(Dali::WindowOrientation::LANDSCAPE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::PORTRAIT_INVERSE); + winHandle.RemoveAvailableOrientation(Dali::WindowOrientation::LANDSCAPE_INVERSE); } else { - winHandle.AddAvailableOrientation(Dali::Window::LANDSCAPE); - winHandle.RemoveAvailableOrientation(Dali::Window::PORTRAIT); - winHandle.AddAvailableOrientation(Dali::Window::LANDSCAPE_INVERSE); - winHandle.RemoveAvailableOrientation(Dali::Window::PORTRAIT_INVERSE); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::LANDSCAPE); + winHandle.RemoveAvailableOrientation(Dali::WindowOrientation::PORTRAIT); + winHandle.AddAvailableOrientation(Dali::WindowOrientation::LANDSCAPE_INVERSE); + winHandle.RemoveAvailableOrientation(Dali::WindowOrientation::PORTRAIT_INVERSE); } CreateFocusEffect();