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