Added Waves example. 34/246734/8
authorGyörgy Straub <g.straub@partner.samsung.com>
Tue, 3 Nov 2020 16:17:07 +0000 (16:17 +0000)
committerGyörgy Straub <g.straub@partner.samsung.com>
Mon, 9 Nov 2020 12:06:29 +0000 (12:06 +0000)
- tilt, or if not supported, pan, to rotate light;
- double tap to trigger color transition;

Change-Id: I7de09c65309f6b069566cf330ebf18904d36a885

com.samsung.dali-demo.xml
demo/dali-demo.cpp
examples/waves/utils.cpp [new file with mode: 0644]
examples/waves/utils.h [new file with mode: 0644]
examples/waves/waves-example.cpp [new file with mode: 0644]
resources/images/noise512.png [new file with mode: 0644]
resources/po/en_GB.po
resources/po/en_US.po
shared/dali-demo-strings.h

index 174f29c..552cefd 100644 (file)
        <ui-application appid="web-view.example" exec="/usr/apps/com.samsung.dali-demo/bin/web-view.example" nodisplay="true" multiple="false" type="c++app" taskmanage="true">
                <label>Web View</label>
        </ui-application>
+       <ui-application appid="waves.example" exec="/usr/apps/com.samsung.dali-demo/bin/waves.example" nodisplay="true" multiple="false" type="c++app" taskmanage="true">
+               <label>Waves</label>
+       </ui-application>
 
        <!-- END OF ALPHABETICAL ORDER SORT. -->
 
index 677c4de..8686bf9 100644 (file)
@@ -38,6 +38,7 @@ int DALI_EXPORT_API main(int argc, char** argv)
   // Create the demo launcher
   DaliTableView demo(app);
 
+  demo.AddExample(Example("animated-gradient-card-active.example", DALI_DEMO_STR_TITLE_CARD_ACTIVE));
   demo.AddExample(Example("blocks.example", DALI_DEMO_STR_TITLE_BLOCKS));
   demo.AddExample(Example("bezier-curve.example", DALI_DEMO_STR_TITLE_BEZIER_CURVE));
   demo.AddExample(Example("bubble-effect.example", DALI_DEMO_STR_TITLE_BUBBLES));
@@ -54,12 +55,11 @@ int DALI_EXPORT_API main(int argc, char** argv)
   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));
-  demo.AddExample(Example("shadows-and-lights.example", DALI_DEMO_STR_TITLE_LIGHTS_AND_SHADOWS));
-  demo.AddExample(Example("sparkle.example", DALI_DEMO_STR_TITLE_SPARKLE));
   demo.AddExample(Example("rendering-skybox.example", DALI_DEMO_STR_TITLE_SKYBOX));
   demo.AddExample(Example("rendering-basic-pbr.example", DALI_DEMO_STR_TITLE_PBR));
-  demo.AddExample(Example("animated-gradient-call-active.example", DALI_DEMO_STR_TITLE_CALL_ACTIVE));
-  demo.AddExample(Example("animated-gradient-card-active.example", DALI_DEMO_STR_TITLE_CARD_ACTIVE));
+  demo.AddExample(Example("shadows-and-lights.example", DALI_DEMO_STR_TITLE_LIGHTS_AND_SHADOWS));
+  demo.AddExample(Example("sparkle.example", DALI_DEMO_STR_TITLE_SPARKLE));
+  demo.AddExample(Example("waves.example", DALI_DEMO_STR_TITLE_WAVES));
 
   demo.SortAlphabetically(true);
 
diff --git a/examples/waves/utils.cpp b/examples/waves/utils.cpp
new file mode 100644 (file)
index 0000000..8446939
--- /dev/null
@@ -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 (file)
index 0000000..ea4366e
--- /dev/null
@@ -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 (file)
index 0000000..993a732
--- /dev/null
@@ -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;
+}
diff --git a/resources/images/noise512.png b/resources/images/noise512.png
new file mode 100644 (file)
index 0000000..b3e85c8
Binary files /dev/null and b/resources/images/noise512.png differ
index 0cc2d8f..a8eb4e1 100755 (executable)
@@ -283,6 +283,9 @@ msgstr "Skybox"
 msgid "DALI_DEMO_STR_TITLE_PBR"
 msgstr "PBR"
 
+msgid "DALI_DEMO_STR_TITLE_WAVES"
+msgstr "Waves"
+
 msgid "DALI_DEMO_STR_TITLE_WEB_VIEW"
 msgstr "Web View"
 
index 23fcbec..6f338f5 100755 (executable)
@@ -300,3 +300,6 @@ msgstr "Text Visual"
 
 msgid "DALI_DEMO_STR_TITLE_TEXT_LABEL_BITMAP_FONT"
 msgstr "Text Bitmap Font"
+
+msgid "DALI_DEMO_STR_TITLE_WAVES"
+msgstr "Waves"
index 5fdff42..889f9ca 100644 (file)
@@ -130,6 +130,7 @@ extern "C"
 #define DALI_DEMO_STR_TITLE_TOOLTIP dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_TOOLTIP")
 #define DALI_DEMO_STR_TITLE_VISUAL_FITTING_MODE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_VISUAL_FITTING_MODE")
 #define DALI_DEMO_STR_TITLE_VISUAL_TRANSITIONS dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_VISUAL_TRANSITIONS")
+#define DALI_DEMO_STR_TITLE_WAVES dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_WAVES")
 #define DALI_DEMO_STR_TITLE_WEB_VIEW dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_WEB_VIEW")
 #define DALI_DEMO_STR_TITLE_TEXT_RENDERER dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_TEXT_RENDERER")
 #define DALI_DEMO_STR_TITLE_TEXT_VISUAL dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_TEXT_VISUAL")
@@ -233,6 +234,7 @@ extern "C"
 #define DALI_DEMO_STR_TITLE_TOOLTIP "Tooltip"
 #define DALI_DEMO_STR_TITLE_VISUAL_FITTING_MODE "Visual Fitting Mode"
 #define DALI_DEMO_STR_TITLE_VISUAL_TRANSITIONS "Visual Transitions"
+#define DALI_DEMO_STR_TITLE_WAVES "Waves"
 #define DALI_DEMO_STR_TITLE_WEB_VIEW "Web View"
 #define DALI_DEMO_STR_TITLE_TEXT_RENDERER "Text Renderer"
 #define DALI_DEMO_STR_TITLE_TEXT_VISUAL "Text Visual"