[dali_2.3.22] Merge branch 'devel/master'
[platform/core/uifw/dali-demo.git] / examples / scene3d-model / scene3d-model-example.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 #include <dali-scene3d/dali-scene3d.h>
19 #include <dali-toolkit/dali-toolkit.h>
20 #include <dali/dali.h>
21 #include <dali/devel-api/adaptor-framework/file-loader.h>
22 #include <dali/devel-api/adaptor-framework/file-stream.h>
23 #include <dali/devel-api/adaptor-framework/image-loading.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/public-api/actors/camera-actor.h>
26 #include <cstring>
27
28 using namespace Dali;
29 using namespace Dali::Toolkit;
30
31 /*
32  * This example shows how to create and display a Model control.
33  * The application can load 5 different glTF model to Model control.
34  * Each model has diffirent material. BoomBox shows glossy or matt plastic material.
35  * DamagedHelmet shows a kind of reflective glass and metallic object.
36  * Microphone shows a roughness of metallic objects.
37  * and Lantern shows a realistic difference between wood object and metallic object.
38  * Rotate the camera by swiping.
39  * A double tap changes the model.
40  */
41
42 namespace
43 {
44 struct ModelInfo
45 {
46   const char*   name;      ///< The name of the model.
47   const Vector2 size;      ///< The size of the model
48   const float   yPosition; ///< The position of the model in the Y axis.
49 };
50
51 const ModelInfo gltf_list[] =
52   {
53     /**
54      * For the BoxAnimated.glb
55      * Donated by Cesium for glTF testing.
56      * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoxAnimated
57      */
58     {"BoxAnimated.glb", Vector2(300.0f, 300.0f), 100.0f},
59     /**
60      * For the quantized Duck.gltf and its Assets
61      * Created by Sony Computer Entertainment Inc.
62      * Licensed under the SCEA Shared Source License, Version 1.0
63      * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Duck/glTF-Quantized
64      */
65     {"Duck.gltf", Vector2(600.0f, 600.0f), 300.0f},
66     /**
67      * For the Lantern.gltf and its Assets
68      * Donated by Microsoft for glTF testing
69      * Created by Ryan Martin
70      * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Lantern
71      */
72     {"Lantern.gltf", Vector2(600.0f, 600.0f), 0.0f},
73     /**
74      * For the BoomBox.gltf and its Assets
75      * Donated by Microsoft for glTF testing
76      * Created by Ryan Martin
77      * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoomBox
78      */
79     {"BoomBox.gltf", Vector2(600.0f, 600.0f), 0.0f},
80     /**
81      * For the DamagedHelmet.glb
82      * Battle Damaged Sci-fi Helmet - PBR by theblueturtle_, published under a
83      * Creative Commons Attribution-NonCommercial license
84      * https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
85      */
86     {"DamagedHelmet.glb", Vector2(600.0f, 600.0f), 0.0f},
87     /**
88      * For the microphone.gltf and its Assets
89      * Microphone GXL 066 Bafhcteks by Gistold, published under a
90      * Creative Commons Attribution-NonCommercial license
91      * https://sketchfab.com/models/5172dbe9281a45f48cee8c15bdfa1831
92      */
93     {"microphone.gltf", Vector2(600.0f, 600.0f), 0.0f},
94     /**
95      * For the beer_model.dli and its Assets
96      * This model includes a bottle of beer and cube box.
97      */
98     {"beer_model.dli", Vector2(600.0f, 600.0f), 0.0f},
99     /**
100      * For the exercise_model.dli and its Assets
101      * This model includes a sportsman
102      */
103     {"exercise_model.dli", Vector2(600.0f, 600.0f), 0.0f},
104 };
105
106 const int32_t NUM_OF_GLTF_MODELS = sizeof(gltf_list) / sizeof(gltf_list[0]);
107
108 /**
109  * For the diffuse and specular cube map texture.
110  * These textures are based off version of Wave engine sample
111  * Take from https://github.com/WaveEngine/Samples
112  *
113  * Copyright (c) 2016 Wave Coorporation
114  *
115  * Permission is hereby granted, free of charge, to any person obtaining a copy
116  * of this software and associated documentation files (the "Software"), to
117  * deal in the Software without restriction, including without limitation the
118  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
119  * sell copies of the Software, and to permit persons to whom the Software is
120  * furnished to do so, subject to the following conditions:
121  *
122  * The above copyright notice and this permission notice shall be included in
123  * all copies or substantial portions of the Software.
124  *
125  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
126  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
127  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
128  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
129  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
130  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
131  * THE SOFTWARE.
132  */
133
134 const std::string modeldir = DEMO_MODEL_DIR;
135 const std::string imagedir = DEMO_IMAGE_DIR;
136 const std::string uri_cube_diffuse_texture(imagedir + "forest_diffuse_cubemap.png");
137 const std::string uri_diffuse_texture(imagedir + "Studio/Irradiance.ktx");
138 const std::string uri_specular_texture(imagedir + "Studio/Radiance.ktx");
139
140 const int32_t cubeMap_index_x[6] = {2, 0, 1, 1, 1, 3};
141 const int32_t cubeMap_index_y[6] = {1, 1, 0, 2, 1, 1};
142
143 const char* VERTEX_SHADER_URL   = DEMO_SHADER_DIR "cube_shader.vsh";
144 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "cube_shader.fsh";
145
146 /**
147  * @brief Load a shader source file
148  * @param[in] The path of the source file
149  * @param[out] The contents of file
150  * @return True if the source was read successfully
151  */
152 bool LoadShaderCode(const std::string& fullpath, std::vector<char>& output)
153 {
154   Dali::FileStream fileStream(fullpath, FileStream::READ | FileStream::BINARY);
155   FILE*            file = fileStream.GetFile();
156   if(nullptr == file)
157   {
158     return false;
159   }
160
161   bool retValue = false;
162   if(!fseek(file, 0, SEEK_END))
163   {
164     long int size = ftell(file);
165
166     if((size != -1L) &&
167        (!fseek(file, 0, SEEK_SET)))
168     {
169       output.resize(size + 1);
170       std::fill(output.begin(), output.end(), 0);
171       ssize_t result = fread(output.data(), size, 1, file);
172
173       retValue = (result >= 0);
174     }
175   }
176
177   return retValue;
178 }
179
180 /**
181  * @brief Load vertex and fragment shader source
182  * @param[in] shaderVertexFileName is the filepath of Vertex shader
183  * @param[in] shaderFragFileName is the filepath of Fragment shader
184  * @return the Dali::Shader object
185  */
186 Shader LoadShaders(const std::string& shaderVertexFileName, const std::string& shaderFragFileName)
187 {
188   Shader            shader;
189   std::vector<char> bufV, bufF;
190
191   if(LoadShaderCode(shaderVertexFileName.c_str(), bufV))
192   {
193     if(LoadShaderCode(shaderFragFileName.c_str(), bufF))
194     {
195       shader = Shader::New(bufV.data(), bufF.data());
196     }
197   }
198   return shader;
199 }
200
201 } // namespace
202
203 /**
204  * This example shows how to render glTF model with Model
205  * How to test
206  *  - Input UP or DOWN key to make the model rotate or stop.
207  *  - Input LEFT or RIGHT key to change glTF model
208  *  - Double Touch also changes glTF model.
209  */
210 class Scene3DModelExample : public ConnectionTracker
211 {
212 public:
213   Scene3DModelExample(Application& application)
214   : mApplication(application),
215     mModelOrientation(),
216     mAnimationStop(false)
217   {
218     // Connect to the Application's Init signal
219     mApplication.InitSignal().Connect(this, &Scene3DModelExample::Create);
220   }
221
222   ~Scene3DModelExample()
223   {
224     mAnimation.Stop();
225   }
226
227   // The Init signal is received once (only) during the Application lifetime
228   void Create(Application& application)
229   {
230     mWindow = application.GetWindow();
231     mWindow.GetRootLayer().SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
232
233     // Get a handle to the mWindow
234     mWindow.SetBackgroundColor(Color::WHITE);
235
236     RenderTask renderTask = mWindow.GetRenderTaskList().GetTask(0);
237     renderTask.SetCullMode(false);
238
239     mCurrentGlTF = 0u;
240     CreateSceneFromGLTF(mCurrentGlTF);
241     SetCameraActor();
242     CreateSkybox();
243     SetAnimation();
244
245     // Respond to a click anywhere on the mWindow
246     mWindow.GetRootLayer().TouchedSignal().Connect(this, &Scene3DModelExample::OnTouch);
247     mWindow.KeyEventSignal().Connect(this, &Scene3DModelExample::OnKeyEvent);
248     mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3DModelExample::OnWheel);
249
250     mDoubleTap     = false;
251     mDoubleTapTime = Timer::New(150);
252     mDoubleTapTime.TickSignal().Connect(this, &Scene3DModelExample::OnDoubleTapTime);
253   }
254
255   bool OnWheel(Actor actor, const WheelEvent& wheelEvent)
256   {
257     mWheelDelta -= wheelEvent.GetDelta() * 0.025f;
258     mWheelDelta = std::max(0.5f, mWheelDelta);
259     mWheelDelta = std::min(2.0f, mWheelDelta);
260
261     if(mModel)
262     {
263       mModel.SetProperty(Actor::Property::SCALE, mWheelDelta);
264     }
265
266     return true;
267   }
268
269   bool OnDoubleTapTime()
270   {
271     mDoubleTap = false;
272     return true;
273   }
274
275   void CreateSceneFromGLTF(uint32_t index)
276   {
277     mReadyToLoad = false;
278     if(mModel)
279     {
280       mWindow.GetRootLayer().Remove(mModel);
281     }
282
283     std::string gltfUrl = modeldir;
284     gltfUrl += gltf_list[index].name;
285
286     mModel = Dali::Scene3D::Model::New(gltfUrl);
287     mModel.SetProperty(Dali::Actor::Property::SIZE, gltf_list[index].size);
288     mModel.SetProperty(Dali::Actor::Property::POSITION_Y, gltf_list[index].yPosition);
289     mModel.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
290     mModel.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
291     mModel.SetImageBasedLightSource(uri_diffuse_texture, uri_specular_texture, 0.6f);
292
293     mModel.ResourceReadySignal().Connect(this, &Scene3DModelExample::ResourceReady);
294
295     mWindow.Add(mModel);
296   }
297
298   void ResourceReady(Control control)
299   {
300     mReadyToLoad = true;
301     if(mModel.GetAnimationCount() > 0)
302     {
303       Animation animation = (std::string("exercise_model.dli") == gltf_list[mCurrentGlTF].name) ? mModel.GetAnimation("idleToSquatClip_0") : mModel.GetAnimation(0u);
304       animation.Play();
305       animation.SetLoopCount(0);
306     }
307   }
308
309   void SetCameraActor()
310   {
311     mCameraActor    = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
312     mCameraPosition = mCameraActor.GetProperty<Vector3>(Dali::Actor::Property::POSITION);
313     mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
314   }
315
316   void CreateSkybox()
317   {
318     struct Vertex
319     {
320       Vector3 aPosition;
321     };
322
323     Vertex skyboxVertices[] = {
324       // back
325       {Vector3(-1.0f, 1.0f, -1.0f)},
326       {Vector3(-1.0f, -1.0f, -1.0f)},
327       {Vector3(1.0f, -1.0f, -1.0f)},
328       {Vector3(1.0f, -1.0f, -1.0f)},
329       {Vector3(1.0f, 1.0f, -1.0f)},
330       {Vector3(-1.0f, 1.0f, -1.0f)},
331
332       // left
333       {Vector3(-1.0f, -1.0f, 1.0f)},
334       {Vector3(-1.0f, -1.0f, -1.0f)},
335       {Vector3(-1.0f, 1.0f, -1.0f)},
336       {Vector3(-1.0f, 1.0f, -1.0f)},
337       {Vector3(-1.0f, 1.0f, 1.0f)},
338       {Vector3(-1.0f, -1.0f, 1.0f)},
339
340       // right
341       {Vector3(1.0f, -1.0f, -1.0f)},
342       {Vector3(1.0f, -1.0f, 1.0f)},
343       {Vector3(1.0f, 1.0f, 1.0f)},
344       {Vector3(1.0f, 1.0f, 1.0f)},
345       {Vector3(1.0f, 1.0f, -1.0f)},
346       {Vector3(1.0f, -1.0f, -1.0f)},
347
348       // front
349       {Vector3(-1.0f, -1.0f, 1.0f)},
350       {Vector3(-1.0f, 1.0f, 1.0f)},
351       {Vector3(1.0f, 1.0f, 1.0f)},
352       {Vector3(1.0f, 1.0f, 1.0f)},
353       {Vector3(1.0f, -1.0f, 1.0f)},
354       {Vector3(-1.0f, -1.0f, 1.0f)},
355
356       // botton
357       {Vector3(-1.0f, 1.0f, -1.0f)},
358       {Vector3(1.0f, 1.0f, -1.0f)},
359       {Vector3(1.0f, 1.0f, 1.0f)},
360       {Vector3(1.0f, 1.0f, 1.0f)},
361       {Vector3(-1.0f, 1.0f, 1.0f)},
362       {Vector3(-1.0f, 1.0f, -1.0f)},
363
364       // top
365       {Vector3(-1.0f, -1.0f, -1.0f)},
366       {Vector3(-1.0f, -1.0f, 1.0f)},
367       {Vector3(1.0f, -1.0f, -1.0f)},
368       {Vector3(1.0f, -1.0f, -1.0f)},
369       {Vector3(-1.0f, -1.0f, 1.0f)},
370       {Vector3(1.0f, -1.0f, 1.0f)}};
371
372     const std::string currentVShaderFile(VERTEX_SHADER_URL);
373     const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
374
375     mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
376
377     Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
378                                                                 .Add("aPosition", Property::VECTOR3));
379     vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
380
381     mSkyboxGeometry = Geometry::New();
382     mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
383     mSkyboxGeometry.SetType(Geometry::TRIANGLES);
384
385     Dali::Scene3D::Loader::EnvironmentMapData environmentMapData;
386     Dali::Scene3D::Loader::LoadEnvironmentMap(uri_cube_diffuse_texture, environmentMapData);
387     Texture texture = environmentMapData.GetTexture();
388
389     mSkyboxTextures = TextureSet::New();
390     mSkyboxTextures.SetTexture(0, texture);
391
392     mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
393     mSkyboxRenderer.SetTextures(mSkyboxTextures);
394     mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
395
396     // Enables the depth test.
397     mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
398
399     // The fragment shader will run only is those pixels that have the max depth value.
400     mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
401
402     mSkyboxActor = Actor::New();
403     mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
404     mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
405     mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
406     mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
407     mSkyboxActor.AddRenderer(mSkyboxRenderer);
408     mWindow.Add(mSkyboxActor);
409   }
410
411   void SetAnimation()
412   {
413     /**
414      * Rotate object Actor along Y axis.
415      * Animation duration is 8 seconds.
416      * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
417      * Each keyframes are interpolated by linear interpolator.
418      */
419     mAnimation                = Animation::New(8.0);
420     KeyFrames keyframes       = KeyFrames::New();
421     float     lengthAnimation = 0.25;
422     for(int32_t i = 0; i < 5; ++i)
423     {
424       keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
425     }
426     mAnimation.AnimateBetween(Property(mModel, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
427     mAnimation.SetLooping(true);
428     mAnimation.Play();
429   }
430
431   uint8_t* CropBuffer(uint8_t* sourceBuffer, uint32_t bytesPerPixel, uint32_t width, uint32_t height, uint32_t xOffset, uint32_t yOffset, uint32_t xFaceSize, uint32_t yFaceSize)
432   {
433     if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
434     {
435       DALI_LOG_ERROR("Can not crop outside of texture area.\n");
436       return nullptr;
437     }
438     uint32_t byteSize   = bytesPerPixel * xFaceSize * yFaceSize;
439     uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
440
441     int32_t srcStride  = width * bytesPerPixel;
442     int32_t destStride = xFaceSize * bytesPerPixel;
443     int32_t srcOffset  = xOffset * bytesPerPixel + yOffset * srcStride;
444     int32_t destOffset = 0;
445     for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
446     {
447       memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
448       srcOffset += srcStride;
449       destOffset += destStride;
450     }
451
452     return destBuffer;
453   }
454
455   void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
456   {
457     uint8_t* imageBuffer   = pixelBuffer.GetBuffer();
458     uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
459     uint32_t imageWidth    = pixelBuffer.GetWidth();
460     uint32_t imageHeight   = pixelBuffer.GetHeight();
461
462     int32_t faceSize = imageWidth / 4;
463
464     uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
465     uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
466
467     uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
468     if(tempImageBuffer)
469     {
470       PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
471       texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
472     }
473   }
474
475   void ChangeModel(int32_t direction)
476   {
477     if(!mReadyToLoad)
478     {
479       return;
480     }
481
482     mCurrentGlTF += direction;
483     if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
484     {
485       mCurrentGlTF = 0;
486     }
487     if(mCurrentGlTF < 0)
488     {
489       mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
490     }
491     CreateSceneFromGLTF(mCurrentGlTF);
492     SetAnimation();
493     mAnimationStop = false;
494     mWheelDelta    = 1.0f;
495   }
496
497   /**
498    * This function will change the material Roughness, Metalness or the model orientation when touched
499    */
500   bool OnTouch(Actor actor, const TouchEvent& touch)
501   {
502     const PointState::Type state = touch.GetState(0);
503
504     switch(state)
505     {
506       case PointState::DOWN:
507       {
508         if(mDoubleTap)
509         {
510           ChangeModel(1);
511         }
512         mDoubleTapTime.Stop();
513
514         mPointZ = touch.GetScreenPosition(0);
515         if(!mAnimationStop)
516         {
517           mAnimation.Pause();
518         }
519         break;
520       }
521
522       case PointState::MOTION:
523       {
524         const Size    size   = mWindow.GetSize();
525         const float   scaleX = size.width;
526         const float   scaleY = size.height;
527         const Vector2 point  = touch.GetScreenPosition(0);
528         mProcess             = false;
529         // If the touch is not processed above, then change the model orientation
530         if(!mProcess)
531         {
532           /**
533            * This is the motion for the swipe to rotate camera that targeting Vector3::ZERO.
534            * Each quaternion is used to rotate camera by Pitch and Yaw respectively.
535            * Final Orientation of camera is combination of Pitch and Yaw quaternion.
536            * For the natural rendering, Skybox also relocated at the new camera position.
537            */
538           mProcess           = true;
539           const float angle1 = ((mPointZ.y - point.y) / scaleY);
540           const float angle2 = ((mPointZ.x - point.x) / scaleX);
541
542           Quaternion pitch(Radian(Degree(angle1 * -200.0f)), (mCameraPosition.z >= 0) ? Vector3::XAXIS : -Vector3::XAXIS);
543           Quaternion yaw(Radian(Degree(angle2 * -200.0f)), -Vector3::YAXIS);
544
545           Quaternion newModelOrientation = yaw * pitch;
546           mCameraPosition                = newModelOrientation.Rotate(mCameraPosition);
547           mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
548           mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
549
550           mPointZ = point;
551         }
552         break;
553       }
554
555       case PointState::UP:
556       {
557         mDoubleTapTime.Start();
558         mDoubleTap = true;
559         if(!mAnimationStop)
560         {
561           mAnimation.Play();
562         }
563         mProcess = false;
564         break;
565       }
566
567       default:
568       {
569         break;
570       }
571     }
572     return true;
573   }
574
575   void OnKeyEvent(const KeyEvent& event)
576   {
577     if(event.GetState() == KeyEvent::DOWN)
578     {
579       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
580       {
581         mApplication.Quit();
582       }
583       if(event.GetKeyName() == "Down" ||
584          event.GetKeyName() == "Up")
585       {
586         if(!mProcess)
587         {
588           if(mAnimationStop)
589           {
590             mAnimation.Play();
591           }
592           else
593           {
594             mAnimation.Pause();
595           }
596           mAnimationStop = !mAnimationStop;
597         }
598       }
599
600       if(IsKey(event, Dali::DALI_KEY_CURSOR_RIGHT))
601       {
602         ChangeModel(1);
603       }
604
605       if(IsKey(event, Dali::DALI_KEY_CURSOR_LEFT))
606       {
607         ChangeModel(-1);
608       }
609     }
610   }
611
612 private:
613   Window       mWindow;
614   Layer        mLayer3D;
615   Application& mApplication;
616   CameraActor  mCameraActor;
617   Dali::Timer  mTimer;
618
619   Vector3              mCameraPosition;
620   Dali::Scene3D::Model mModel;
621
622   Vector2    mPointZ;
623   Quaternion mModelOrientation;
624
625   Shader     mShaderSkybox;
626   Geometry   mSkyboxGeometry;
627   TextureSet mSkyboxTextures;
628   Renderer   mSkyboxRenderer;
629   Actor      mSkyboxActor;
630
631   Animation mAnimation;
632   bool      mProcess{false};
633   bool      mAnimationStop;
634
635   Timer mDoubleTapTime;
636   bool  mDoubleTap{false};
637
638   float mWheelDelta{1.0f};
639
640   int32_t mCurrentGlTF{0};
641
642   bool mReadyToLoad{true};
643 };
644
645 int32_t DALI_EXPORT_API main(int32_t argc, char** argv)
646 {
647   Application         application = Application::New(&argc, &argv);
648   Scene3DModelExample test(application);
649   application.MainLoop();
650   return 0;
651 }