summaryrefslogtreecommitdiff
path: root/examples/waves
diff options
context:
space:
mode:
authorGyörgy Straub <g.straub@partner.samsung.com>2020-11-03 16:17:07 +0000
committerGyörgy Straub <g.straub@partner.samsung.com>2020-11-09 12:06:29 +0000
commit475e959d61138abb18e3192f5e7accf294427fe0 (patch)
tree28e62d49de15da861d54c288a20b6c7cd4ed0c97 /examples/waves
parent770d03150043856efc3d663eca552714e71ca374 (diff)
downloaddali-demo-475e959d61138abb18e3192f5e7accf294427fe0.tar.gz
dali-demo-475e959d61138abb18e3192f5e7accf294427fe0.tar.bz2
dali-demo-475e959d61138abb18e3192f5e7accf294427fe0.zip
Added Waves example.
- tilt, or if not supported, pan, to rotate light; - double tap to trigger color transition; Change-Id: I7de09c65309f6b069566cf330ebf18904d36a885
Diffstat (limited to 'examples/waves')
-rw-r--r--examples/waves/utils.cpp307
-rw-r--r--examples/waves/utils.h115
-rw-r--r--examples/waves/waves-example.cpp547
3 files changed, 969 insertions, 0 deletions
diff --git a/examples/waves/utils.cpp b/examples/waves/utils.cpp
new file mode 100644
index 00000000..84469398
--- /dev/null
+++ b/examples/waves/utils.cpp
@@ -0,0 +1,307 @@
+/*
+ * 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 "dali-toolkit/dali-toolkit.h"
+#include <fstream>
+
+using namespace Dali;
+using namespace Dali::Toolkit;
+
+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 CreateTesselatedQuad(unsigned int xVerts, unsigned int yVerts,
+ Vector2 scale, VertexFn positionFn, VertexFn texCoordFn)
+{
+ DALI_ASSERT_DEBUG(xVerts > 1 && yVerts > 1);
+ int numVerts = xVerts * yVerts;
+ struct Vertex
+ {
+ Vector2 aPosition;
+ Vector2 aTexCoord;
+ };
+ std::vector<Vertex> vertices;
+ vertices.reserve( numVerts);
+
+ float dx = 1.f / (xVerts - 1);
+ float dz = 1.f / (yVerts - 1);
+
+ Vector2 pos{ 0.f, 0.f };
+ for (unsigned int i = 0; i < yVerts; ++i)
+ {
+ pos.x = float(int((i & 1) * 2) - 1) * dx * .25f;
+ for (unsigned int j = 0; j < xVerts; ++j)
+ {
+ auto vPos = pos + Vector2{ -.5f, -.5f };
+ vertices.push_back(Vertex{ (positionFn ? positionFn(vPos) : vPos) * scale,
+ texCoordFn ? texCoordFn(pos) : pos });
+ pos.x += dx;
+ }
+
+ pos.y += dz;
+ }
+
+ VertexBuffer vertexBuffer = VertexBuffer::New( Property::Map()
+ .Add( "aPosition", Property::VECTOR2 )
+ .Add( "aTexCoord", Property::VECTOR2 ));
+ vertexBuffer.SetData(vertices.data(), vertices.size());
+
+ int numInds = (xVerts - 1) * (yVerts - 1) * 6;
+ std::vector<uint16_t> indices;
+ indices.reserve(numInds);
+
+ for (unsigned int i = 1; i < yVerts; ++i)
+ {
+ if ((i & 1) == 0)
+ {
+ for (unsigned int j = 1; j < xVerts; ++j)
+ {
+ int iBase = i * xVerts + j;
+ indices.push_back(iBase);
+ indices.push_back(iBase - 1);
+ indices.push_back(iBase - xVerts - 1);
+ indices.push_back(indices.back());
+ indices.push_back(iBase - xVerts);
+ indices.push_back(iBase);
+ }
+ }
+ else
+ {
+ for (unsigned int j = 1; j < xVerts; ++j)
+ {
+ int iBase = i * xVerts + j;
+ indices.push_back(iBase);
+ indices.push_back(iBase - 1);
+ indices.push_back(iBase - xVerts);
+ indices.push_back(indices.back());
+ indices.push_back(iBase - 1);
+ indices.push_back(iBase - xVerts - 1);
+ }
+ }
+ }
+
+ Geometry geom = Geometry::New();
+ geom.AddVertexBuffer(vertexBuffer);
+ geom.SetIndexBuffer(indices.data(), indices.size());
+ return geom;
+}
+
+Texture LoadTexture(const std::string& path)
+{
+ PixelData pixelData = SyncImageLoader::Load(path);
+
+ Texture texture = Texture::New(TextureType::TEXTURE_2D, pixelData.GetPixelFormat(),
+ pixelData.GetWidth(), pixelData.GetHeight());
+ texture.Upload(pixelData);
+ return texture;
+}
+
+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 = Dali::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/waves/utils.h b/examples/waves/utils.h
new file mode 100644
index 00000000..ea4366ee
--- /dev/null
+++ b/examples/waves/utils.h
@@ -0,0 +1,115 @@
+#ifndef WAVES_UTILS_H_
+#define WAVES_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/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 <cmath>
+
+//
+// Maths
+//
+inline
+float FastFloor(float x)
+{
+ return static_cast<int>(x) - static_cast<int>(x < 0);
+}
+
+inline
+float Sign(float x)
+{
+ return float(x > 0.f) - float(x < .0f);
+}
+
+template <typename T>
+inline
+typename std::decay<T>::type Lerp(
+ const T& min, const T& max, float alpha)
+{
+ return min + (max - min) * alpha;
+}
+
+template <typename T>
+T Normalized(T v)
+{
+ v.Normalize();
+ return v;
+}
+
+//
+// Files
+//
+///@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
+//
+using VertexFn = Dali::Vector2(*)(const Dali::Vector2&);
+
+///@brief Creates a tesselated quad with @a xVerts vertices horizontally and @a yVerts
+/// vertices vertically. Allows the use of an optional @a shaderFn, which can be used to
+/// modify the vertex positions - these will be in the [{ 0.f, 0.f}, { 1.f, 1.f}] range.
+/// After returning from the shader, they're transformed
+Dali::Geometry CreateTesselatedQuad(unsigned int xVerts, unsigned int yVerts,
+ Dali::Vector2 scale, VertexFn positionFn = nullptr, VertexFn texCoordFn = nullptr);
+
+Dali::Texture LoadTexture(const std::string& path);
+
+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 /* EXAMPLES_PARTICLES_UTILS_H_ */
diff --git a/examples/waves/waves-example.cpp b/examples/waves/waves-example.cpp
new file mode 100644
index 00000000..993a732a
--- /dev/null
+++ b/examples/waves/waves-example.cpp
@@ -0,0 +1,547 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 ( "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 "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/animation/animation.h"
+#include "dali/public-api/events/pan-gesture-detector.h"
+#include "dali/public-api/events/tap-gesture-detector.h"
+#include "dali/public-api/events/key-event.h"
+#include "dali/public-api/actors/camera-actor.h"
+#include "dali/public-api/actors/layer.h"
+#include "dali/public-api/render-tasks/render-task.h"
+#include "dali/public-api/render-tasks/render-task-list.h"
+#include <fstream>
+#include <iostream>
+#include <numeric>
+
+using namespace Dali;
+
+namespace
+{
+
+constexpr std::string_view WAVES_VSH =
+ "#define FMA(a, b, c) ((a) * (b) + (c))\n" // fused multiply-add
+DALI_COMPOSE_SHADER(
+ precision highp float;
+
+ const float kTile = 1.;
+
+ const float kPi = 3.1415926535;
+ const float kEpsilon = 1. / 32.;
+
+ // DALI uniforms
+ uniform vec3 uSize;
+ uniform mat4 uModelView;
+ uniform mat4 uProjection;
+ uniform mat3 uNormalMatrix;
+
+ // our uniforms
+ uniform float uTime;
+ uniform vec2 uScrollScale;
+ uniform float uWaveRate;
+ uniform float uWaveAmplitude;
+ uniform float uParallaxAmount;
+
+ attribute vec2 aPosition;
+ attribute vec2 aTexCoord;
+
+ varying vec2 vUv;
+ varying vec3 vViewPos;
+ varying vec3 vNormal;
+ varying float vHeight;
+
+ float CubicHermite(float B, float C, float t)
+ {
+ float dCB = (C - B) * .5;
+ float A = B - dCB;
+ float D = B + dCB;
+ vec3 p = vec3(D + .5 * (((B - C) * 3.) - A), A - 2.5 * B + 2. * C - D,
+ .5 * (C - A));
+ return FMA(FMA(FMA(p.x, t, p.y), t, p.z), t, B);
+ }
+
+ float Hash(float n)
+ {
+ return fract(sin(n) * 43751.5453123);
+ }
+
+ float HeightAtTile(vec2 pos)
+ {
+ float rate = Hash(Hash(pos.x) * Hash(pos.y));
+
+ return (sin(uTime * rate * uWaveRate) * .5 + .5) * uWaveAmplitude;
+ }
+
+ float CalculateHeight(vec2 position)
+ {
+ vec2 tile = floor(position);
+ position = fract(position);
+
+ vec2 cp = vec2(
+ CubicHermite(
+ HeightAtTile(tile + vec2( kTile * -0.5, kTile * -0.5)),
+ HeightAtTile(tile + vec2( kTile * +0.5, kTile * -0.5)),
+ position.x),
+ CubicHermite(
+ HeightAtTile(tile + vec2( kTile * -0.5, kTile * +0.5)),
+ HeightAtTile(tile + vec2( kTile * +0.5, kTile * +0.5)),
+ position.x)
+ );
+
+ return CubicHermite(cp.x, cp.y, position.y);
+ }
+
+ vec3 CalculateNormal(vec2 position)
+ {
+ vec3 normal = vec3(
+ CalculateHeight(vec2(position.x - kEpsilon, position.y)) -
+ CalculateHeight(vec2(position.x + kEpsilon, position.y)),
+ .25,
+ CalculateHeight(vec2(position.x, position.y - kEpsilon)) -
+ CalculateHeight(vec2(position.x, position.y + kEpsilon))
+ );
+ return normal;
+ }
+
+ void main()
+ {
+ vUv = aTexCoord;
+
+ vec2 scrollPosition = aPosition * uScrollScale + vec2(0., uTime * -kPi);
+ vNormal = uNormalMatrix * CalculateNormal(scrollPosition);
+
+ float h = CalculateHeight(scrollPosition);
+ vHeight = h * uParallaxAmount;
+ vec3 position = vec3(aPosition.x, h, aPosition.y);
+
+ vec4 viewPosition = uModelView * vec4(position * uSize, 1.);
+ vViewPos = -viewPosition.xyz;
+
+ gl_Position = uProjection * viewPosition;
+ });
+
+constexpr std::string_view WAVES_FSH = DALI_COMPOSE_SHADER(
+ precision highp float;
+
+ uniform vec4 uColor; // DALi
+ uniform sampler2D uNormalMap; // DALi
+
+ uniform vec3 uInvLightDir;
+ uniform vec3 uLightColorSqr;
+ uniform vec3 uAmbientColor;
+
+ uniform float uNormalMapWeight;
+ uniform float uSpecularity;
+
+ varying vec2 vUv;
+ varying vec3 vNormal;
+ varying vec3 vViewPos;
+ varying float vHeight;
+
+ float Rand(vec2 co)
+ {
+ return fract(sin(dot(co.xy, vec2(12.98981, 78.2331))) * 43758.5453);
+ }
+
+ float Sum(vec3 v)
+ {
+ return v.x + v.y + v.z;
+ }
+
+ void main()
+ {
+ vec3 viewPos = normalize(vViewPos);
+ vec2 uv2 = vUv + vViewPos.xy / vViewPos.z * vHeight + vec2(.5, 0.);
+
+ vec3 perturbNormal = texture2D(uNormalMap, vUv).rgb * 2. - 1.;
+ vec3 perturbNormal2 = texture2D(uNormalMap, uv2).rgb * 2. - 1.;
+ vec3 normal = normalize(vNormal + perturbNormal * uNormalMapWeight);
+ vec3 normal2 = normalize(vNormal + perturbNormal2 * uNormalMapWeight);
+
+ vec3 color = uAmbientColor;
+ float d = max(0., dot(normal, -uInvLightDir));
+ color += uColor.rgb * d;
+
+ vec3 reflected = reflect(uInvLightDir, normal);
+ d = max(0., dot(reflected, viewPos));
+ color += pow(d, uSpecularity) * uLightColorSqr;
+
+ reflected = reflect(uInvLightDir, normal2);
+ d = max(0., dot(reflected, viewPos));
+ color += pow(d, uSpecularity) * uLightColorSqr;
+
+ gl_FragColor = vec4(color, 1.);
+ });
+
+const float TIME_STEP = 0.0952664626;
+
+const std::string UNIFORM_LIGHT_COLOR_SQR = "uLightColorSqr";
+const std::string UNIFORM_AMBIENT_COLOR = "uAmbientColor";
+const std::string UNIFORM_INV_LIGHT_DIR = "uInvLightDir";
+const std::string UNIFORM_SCROLL_SCALE = "uScrollScale";
+const std::string UNIFORM_WAVE_RATE = "uWaveRate";
+const std::string UNIFORM_WAVE_AMPLITUDE = "uWaveAmplitude";
+const std::string UNIFORM_NORMAL_MAP_WEIGHT = "uNormalMapWeight";
+const std::string UNIFORM_SPECULARITY = "uSpecularity";
+const std::string UNIFORM_PARALLAX_AMOUNT = "uParallaxAmount";
+const std::string UNIFORM_TIME = "uTime";
+
+const Vector3 WAVES_COLOR { .78f, .64f, .26f };
+const Vector3 LIGHT_COLOR { 1.0f, 0.91f, 0.6f };
+const Vector3 AMBIENT_COLOR { .002f, .001f, .001f };
+
+const Vector3 INV_LIGHT_DIR = Normalized(Vector3{ .125f, .8f, -.55f });
+
+const Vector2 SCROLL_SCALE{ 1.f, 3.5f };
+const float WAVE_RATE = 12.17f;
+const float WAVE_AMPLITUDE = 1.f;
+const float NORMAL_MAP_WEIGHT = 0.05f;
+const float SPECULARITY = 512.f;
+const float PARALLAX_AMOUNT = .25f;
+
+const float TILT_RANGE_DEGREES = 30.f;
+
+const float TRANSITION_DURATION = 1.2f;
+const float TRANSITION_TIME_SCALE = 6.f;
+
+const std::string_view NORMAL_MAP_NAME = "noise512.png";
+
+Vector3 RandomColor()
+{
+ float r = .5f + (rand() % RAND_MAX) / float(RAND_MAX) * .5f;
+ float g = .5f + (rand() % RAND_MAX) / float(RAND_MAX) * .5f;
+ float b = .5f + (rand() % RAND_MAX) / float(RAND_MAX) * .5f;
+ return Vector3(r, g, b);
+}
+
+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;
+};
+
+} // nonamespace
+
+class WavesExample : public ConnectionTracker
+{
+public:
+ WavesExample( Application& app )
+ : mApp( app )
+ {
+ mApp.InitSignal().Connect( this, &WavesExample::Create );
+ mApp.TerminateSignal().Connect( this, &WavesExample::Destroy );
+ }
+
+ ~WavesExample() = default;
+
+private:
+ Application& mApp;
+
+ CameraActor mCamera; // no ownership
+
+ Actor mWaves;
+ Shader mWaveShader;
+
+ Property::Index mUInvLightDir;
+ Property::Index mULightColorSqr;
+ Property::Index mUAmbientColor;
+ Property::Index mUWaveRate;
+ Property::Index mUWaveAmplitude;
+ Property::Index mUScrollScale;
+ Property::Index mUNormalMapWeight;
+ Property::Index mUSpecularity;
+ Property::Index mUParallaxAmount;
+ Property::Index mUTime;
+
+ TapGestureDetector mDoubleTapGesture;
+
+ TiltSensor mTiltSensor;
+ TiltFilter mTiltFilter;
+
+ PanGestureDetector mPanGesture;
+
+ Animation mTimeAnim;
+ Animation mTransitionAnim;
+
+ void Create( Application& application )
+ {
+ Window window = application.GetWindow();
+ auto rootLayer = window.GetRootLayer();
+
+ window.SetBackgroundColor(Vector4(WAVES_COLOR * .5f));
+
+ // Get camera
+ RenderTaskList tasks = window.GetRenderTaskList();
+ RenderTask mainPass = tasks.GetTask(0);
+ CameraActor camera = mainPass.GetCameraActor();
+ mCamera = camera;
+
+ // NOTE: watchface doesn't tolerate modification of the camera well;
+ /// we're better off rotating the world.
+ Quaternion baseOrientation (Radian(Degree(-150.f)), Radian(M_PI), Radian(0.f));
+
+ auto shader = CreateShader();
+
+ // Create geometry
+ Geometry geom = CreateTesselatedQuad(16, 64, Vector2{ .25f, 3.8f }, [](const Vector2& v) {
+ float y = v.y + .5f; // 0..1
+ y = std::sqrt(y) - .5f; // perspective correction - increase vertex density closer to viewer
+
+ float x = v.x + v.x * (1.f - y) * 5.5f;
+
+ y -= .24f; // further translation
+ return Vector2{ x, y };
+ }, [](const Vector2& v) {
+ return Vector2{ v.x, std::sqrt(v.y) };
+ });
+
+ // Create texture
+ auto normalMap = LoadTexture(std::string(DEMO_IMAGE_DIR) + NORMAL_MAP_NAME.data());
+
+ TextureSet textures = TextureSet::New();
+ textures.SetTexture(0, normalMap);
+
+ Sampler sampler = Sampler::New();
+ sampler.SetFilterMode(FilterMode::NEAREST, FilterMode::NEAREST);
+ sampler.SetWrapMode(WrapMode::REPEAT, WrapMode::REPEAT);
+ textures.SetSampler(0, sampler);
+
+ // Create renderer
+ Renderer renderer = CreateRenderer(textures, geom, shader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE);
+
+ auto waves = CreateActor();
+ auto size = Vector2(window.GetSize());
+ waves.SetProperty(Actor::Property::SIZE, Vector3(size.x, 100.f, size.y));
+ waves.SetProperty(Actor::Property::ORIENTATION, baseOrientation);
+ waves.SetProperty(Actor::Property::COLOR, WAVES_COLOR);
+ waves.AddRenderer(renderer);
+
+ window.Add(waves);
+ mWaves = waves;
+
+ window.KeyEventSignal().Connect( this, &WavesExample::OnKeyEvent );
+
+ // Setup double tap detector for color change
+ mDoubleTapGesture = TapGestureDetector::New(2);
+ mDoubleTapGesture.Attach(rootLayer);
+ mDoubleTapGesture.DetectedSignal().Connect(this, &WavesExample::OnDoubleTap);
+
+ // Touch controls
+ mTiltSensor = TiltSensor::Get();
+ if ( mTiltSensor.Start() )
+ {
+ // Get notifications when the device is tilted
+ mTiltSensor.TiltedSignal().Connect( this, &WavesExample::OnTilted );
+ }
+ else
+ {
+ mPanGesture = PanGestureDetector::New();
+ mPanGesture.Attach(rootLayer);
+ mPanGesture.DetectedSignal().Connect(this, &WavesExample::OnPan);
+ }
+
+ // Register for suspend / resume
+ application.PauseSignal().Connect(this, &WavesExample::OnPause);
+ application.ResumeSignal().Connect(this, &WavesExample::OnResume);
+
+ // Create animation for the simulation of time
+ Animation animTime = Animation::New(1.f);
+ animTime.AnimateBy(Property(mWaveShader, mUTime), TIME_STEP);
+ animTime.FinishedSignal().Connect(this, &WavesExample::OnTimeAnimFinished);
+ animTime.Play();
+ mTimeAnim = animTime;
+ }
+
+ void Destroy( Application& app)
+ {
+ mCamera.Reset();
+
+ mDoubleTapGesture.Reset();
+ mPanGesture.Reset();
+
+ UnparentAndReset(mWaves);
+ }
+
+ Shader CreateShader()
+ {
+ Vector3 lightColorSqr{ LIGHT_COLOR };
+ Vector3 ambientColor = AMBIENT_COLOR;
+ Vector3 invLightDir = INV_LIGHT_DIR;
+ Vector2 scrollScale = SCROLL_SCALE;
+ float waveRate = WAVE_RATE;
+ float waveAmp = WAVE_AMPLITUDE;
+ float normalMapWeight = NORMAL_MAP_WEIGHT;
+ float specularity = SPECULARITY;
+ float parallaxAmount = PARALLAX_AMOUNT;
+ if (mWaveShader)
+ {
+ lightColorSqr = mWaveShader.GetProperty(mULightColorSqr).Get<Vector3>();
+ ambientColor = mWaveShader.GetProperty(mUAmbientColor).Get<Vector3>();
+ invLightDir = mWaveShader.GetProperty(mUInvLightDir).Get<Vector3>();
+ scrollScale = mWaveShader.GetProperty(mUScrollScale).Get<Vector2>();
+ waveRate = mWaveShader.GetProperty(mUWaveRate).Get<float>();
+ waveAmp = mWaveShader.GetProperty(mUWaveAmplitude).Get<float>();
+ normalMapWeight = mWaveShader.GetProperty(mUNormalMapWeight).Get<float>();
+ specularity = mWaveShader.GetProperty(mUSpecularity).Get<float>();
+ }
+
+ Shader shader = Shader::New(WAVES_VSH.data(), WAVES_FSH.data(), Shader::Hint::MODIFIES_GEOMETRY);
+ mULightColorSqr = shader.RegisterProperty(UNIFORM_LIGHT_COLOR_SQR, lightColorSqr);
+ mUAmbientColor = shader.RegisterProperty(UNIFORM_AMBIENT_COLOR, ambientColor);
+ mUInvLightDir = shader.RegisterProperty(UNIFORM_INV_LIGHT_DIR, invLightDir);
+ mUScrollScale = shader.RegisterProperty(UNIFORM_SCROLL_SCALE, scrollScale);
+ mUWaveRate = shader.RegisterProperty(UNIFORM_WAVE_RATE, waveRate);
+ mUWaveAmplitude = shader.RegisterProperty(UNIFORM_WAVE_AMPLITUDE, waveAmp);
+ mUNormalMapWeight = shader.RegisterProperty(UNIFORM_NORMAL_MAP_WEIGHT, normalMapWeight);
+ mUSpecularity = shader.RegisterProperty(UNIFORM_SPECULARITY, specularity);
+ mUParallaxAmount = shader.RegisterProperty(UNIFORM_PARALLAX_AMOUNT, parallaxAmount);
+ mUTime = shader.RegisterProperty(UNIFORM_TIME, 0.f);
+
+ auto window = mApp.GetWindow();
+ shader.RegisterProperty("uScreenHalfSize", Vector2(window.GetSize()) * .5f);
+ mWaveShader = shader;
+
+ return shader;
+ }
+
+ void TriggerColorTransition(Vector3 wavesColor, Vector3 lightColor)
+ {
+ if (mTransitionAnim)
+ {
+ mTransitionAnim.Stop();
+ }
+
+ mTimeAnim.FinishedSignal().Disconnect(this, &WavesExample::OnTimeAnimFinished);
+ mTimeAnim.Stop();
+
+ Animation anim = Animation::New(TRANSITION_DURATION);
+ anim.AnimateTo(Property(mWaves, Actor::Property::COLOR), Vector4(wavesColor), AlphaFunction::EASE_IN_OUT);
+ anim.AnimateTo(Property(mWaveShader, mULightColorSqr), lightColor * lightColor, AlphaFunction::EASE_IN_OUT);
+ anim.AnimateBy(Property(mWaveShader, mUTime), TRANSITION_DURATION * TIME_STEP * TRANSITION_TIME_SCALE, AlphaFunction::EASE_IN_OUT);
+ anim.FinishedSignal().Connect(this, &WavesExample::OnTransitionFinished);
+ anim.Play();
+ mTransitionAnim = anim;
+ }
+
+ void OnTimeAnimFinished(Animation& anim)
+ {
+ anim.Play();
+ }
+
+ void OnTransitionFinished(Animation& anim)
+ {
+ mTransitionAnim.Reset();
+ mTimeAnim.FinishedSignal().Connect(this, &WavesExample::OnTimeAnimFinished);
+ mTimeAnim.Play();
+ }
+
+ void OnPause(Application& app)
+ {
+ mTimeAnim.Pause();
+ mTiltSensor.Stop();
+ }
+
+ void OnResume(Application& app)
+ {
+ mTiltSensor.Start();
+ mTimeAnim.Play();
+ }
+
+ 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();
+ }
+ }
+ }
+
+ void OnDoubleTap(Actor /*actor*/, const TapGesture& gesture)
+ {
+ Vector3 lightColor = mWaveShader.GetProperty(mULightColorSqr).Get<Vector3>();
+ TriggerColorTransition(lightColor, RandomColor());
+ }
+
+ void OnPan(Actor actor, const PanGesture& gesture)
+ {
+ auto tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize());
+ switch (gesture.GetState())
+ {
+ case GestureState::STARTED:
+ mTiltFilter.Add(tilt);
+ break;
+
+ case GestureState::CONTINUING:
+ mTiltFilter.Add(mTiltFilter.Filter() + tilt);
+ break;
+
+ default:
+ break;
+ }
+
+ UpdateLightDirection();
+ }
+
+ void OnTilted( const TiltSensor& sensor)
+ {
+ mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll()));
+
+ UpdateLightDirection();
+ }
+
+ void UpdateLightDirection()
+ {
+ Vector2 tilt = mTiltFilter.Filter();
+ Quaternion q(Radian(tilt.y), Radian(-tilt.x), Radian(0.f));
+ Vector3 lightDir = q.Rotate(INV_LIGHT_DIR);
+ mWaveShader.SetProperty(mUInvLightDir, lightDir);
+ }
+};
+
+int DALI_EXPORT_API main( int argc, char **argv )
+{
+ Application application = Application::New( &argc, &argv, DEMO_THEME_PATH );
+ WavesExample example( application);
+ application.MainLoop();
+ return 0;
+}