diff options
author | György Straub <g.straub@partner.samsung.com> | 2020-11-03 16:17:07 +0000 |
---|---|---|
committer | György Straub <g.straub@partner.samsung.com> | 2020-11-09 12:06:29 +0000 |
commit | 475e959d61138abb18e3192f5e7accf294427fe0 (patch) | |
tree | 28e62d49de15da861d54c288a20b6c7cd4ed0c97 /examples/waves | |
parent | 770d03150043856efc3d663eca552714e71ca374 (diff) | |
download | dali-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.cpp | 307 | ||||
-rw-r--r-- | examples/waves/utils.h | 115 | ||||
-rw-r--r-- | examples/waves/waves-example.cpp | 547 |
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; +} |