a257f562f8303b1e6d43f690abdd3351196b6492
[platform/core/uifw/dali-demo.git] / examples / scene3d-model-view / scene3d-model-view-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-view/model-view.h>
28
29 using namespace Dali;
30 using namespace Dali::Toolkit;
31
32 /*
33  * This example shows how to create and display a ModelView control.
34  * The application can load 5 different glTF model to ModelView 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_modelView.dli and its Assets
86      * This model includes a bottle of beer and cube box.
87      */
88     "beer_modelView.dli",
89     /**
90      * For the exercise_modelView.dli and its Assets
91      * This model includes a sportsman
92      */
93     "exercise_modelView.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 ModelView
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 Scene3DModelViewExample : public ConnectionTracker
198 {
199 public:
200   Scene3DModelViewExample(Application& application)
201   : mApplication(application),
202     mModelOrientation(),
203     mAnimationStop(false)
204   {
205     // Connect to the Application's Init signal
206     mApplication.InitSignal().Connect(this, &Scene3DModelViewExample::Create);
207   }
208
209   ~Scene3DModelViewExample()
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     // Get a handle to the mWindow
220     mWindow.SetBackgroundColor(Color::WHITE);
221
222     RenderTask renderTask = mWindow.GetRenderTaskList().GetTask(0);
223     renderTask.SetCullMode(false);
224
225     mCurrentGlTF = 0u;
226     CreateSceneFromGLTF(mCurrentGlTF);
227     SetCameraActor();
228     CreateSkybox();
229     SetAnimation();
230
231     // Respond to a click anywhere on the mWindow
232     mWindow.GetRootLayer().TouchedSignal().Connect(this, &Scene3DModelViewExample::OnTouch);
233     mWindow.KeyEventSignal().Connect(this, &Scene3DModelViewExample::OnKeyEvent);
234     mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3DModelViewExample::OnWheel);
235
236     mDoubleTap     = false;
237     mDoubleTapTime = Timer::New(150);
238     mDoubleTapTime.TickSignal().Connect(this, &Scene3DModelViewExample::OnDoubleTapTime);
239   }
240
241   bool OnWheel(Actor actor, const WheelEvent& wheelEvent)
242   {
243     mWheelDelta -= wheelEvent.GetDelta() * 0.025f;
244     mWheelDelta = std::max(0.5f, mWheelDelta);
245     mWheelDelta = std::min(2.0f, mWheelDelta);
246
247     if(mModelView)
248     {
249       mModelView.SetProperty(Actor::Property::SCALE, mWheelDelta);
250     }
251
252     return true;
253   }
254
255   bool OnDoubleTapTime()
256   {
257     mDoubleTap = false;
258     return true;
259   }
260
261   void CreateSceneFromGLTF(uint32_t index)
262   {
263     if(mModelView)
264     {
265       mWindow.GetRootLayer().Remove(mModelView);
266     }
267
268     std::string gltfUrl = modeldir;
269     gltfUrl += gltf_list[index];
270
271     mModelView = Dali::Scene3D::ModelView::New(gltfUrl);
272     if(index == 0u)
273     {
274       mModelView.SetProperty(Dali::Actor::Property::SIZE, Vector2(300, 300));
275       mModelView.SetProperty(Dali::Actor::Property::POSITION_Y, 100);
276     }
277     else
278     {
279       mModelView.SetProperty(Dali::Actor::Property::SIZE, Vector2(600, 600));
280     }
281     mModelView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
282     mModelView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
283     mModelView.SetImageBasedLightSource(uri_diffuse_texture, uri_specular_texture, 0.6f);
284     mModelView.FitSize(true);
285     mModelView.FitCenter(true);
286
287     mWindow.Add(mModelView);
288     if(mModelView.GetAnimationCount()>0)
289     {
290       Animation animation = (index == 0u) ? mModelView.GetAnimation(0u) : mModelView.GetAnimation("idleToSquatClip_0");
291       animation.Play();
292       animation.SetLoopCount(0);
293     }
294   }
295
296   void SetCameraActor()
297   {
298     mCameraActor    = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
299     mCameraPosition = mCameraActor.GetProperty<Vector3>(Dali::Actor::Property::POSITION);
300     mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
301   }
302
303   void CreateSkybox()
304   {
305     struct Vertex
306     {
307       Vector3 aPosition;
308     };
309
310     Vertex skyboxVertices[] = {
311       // back
312       {Vector3(-1.0f, 1.0f, -1.0f)},
313       {Vector3(-1.0f, -1.0f, -1.0f)},
314       {Vector3(1.0f, -1.0f, -1.0f)},
315       {Vector3(1.0f, -1.0f, -1.0f)},
316       {Vector3(1.0f, 1.0f, -1.0f)},
317       {Vector3(-1.0f, 1.0f, -1.0f)},
318
319       // left
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       {Vector3(-1.0f, 1.0f, -1.0f)},
324       {Vector3(-1.0f, 1.0f, 1.0f)},
325       {Vector3(-1.0f, -1.0f, 1.0f)},
326
327       // right
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       {Vector3(1.0f, 1.0f, 1.0f)},
332       {Vector3(1.0f, 1.0f, -1.0f)},
333       {Vector3(1.0f, -1.0f, -1.0f)},
334
335       // front
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       {Vector3(1.0f, 1.0f, 1.0f)},
340       {Vector3(1.0f, -1.0f, 1.0f)},
341       {Vector3(-1.0f, -1.0f, 1.0f)},
342
343       // botton
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       {Vector3(1.0f, 1.0f, 1.0f)},
348       {Vector3(-1.0f, 1.0f, 1.0f)},
349       {Vector3(-1.0f, 1.0f, -1.0f)},
350
351       // top
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       {Vector3(1.0f, -1.0f, -1.0f)},
356       {Vector3(-1.0f, -1.0f, 1.0f)},
357       {Vector3(1.0f, -1.0f, 1.0f)}};
358
359     const std::string currentVShaderFile(VERTEX_SHADER_URL);
360     const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
361
362     mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
363
364     Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
365                                                                 .Add("aPosition", Property::VECTOR3));
366     vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
367
368     mSkyboxGeometry = Geometry::New();
369     mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
370     mSkyboxGeometry.SetType(Geometry::TRIANGLES);
371
372     // Diffuse Cube Map
373     Devel::PixelBuffer diffusePixelBuffer = LoadImageFromFile(uri_cube_diffuse_texture);
374     int32_t            diffuseFaceSize    = diffusePixelBuffer.GetWidth() / 4;
375     Texture            texture            = Texture::New(TextureType::TEXTURE_CUBE, diffusePixelBuffer.GetPixelFormat(), diffuseFaceSize, diffuseFaceSize);
376     for(int32_t i = 0; i < 6; ++i)
377     {
378       UploadTextureFace(texture, diffusePixelBuffer, i);
379     }
380     texture.GenerateMipmaps();
381
382     mSkyboxTextures = TextureSet::New();
383     mSkyboxTextures.SetTexture(0, texture);
384
385     mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
386     mSkyboxRenderer.SetTextures(mSkyboxTextures);
387     mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
388
389     // Enables the depth test.
390     mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
391
392     // The fragment shader will run only is those pixels that have the max depth value.
393     mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
394
395     mSkyboxActor = Actor::New();
396     mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
397     mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
398     mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
399     mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
400     mSkyboxActor.AddRenderer(mSkyboxRenderer);
401     mWindow.Add(mSkyboxActor);
402   }
403
404   void SetAnimation()
405   {
406     /**
407      * Rotate object Actor along Y axis.
408      * Animation duration is 8 seconds.
409      * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
410      * Each keyframes are interpolated by linear interpolator.
411      */
412     mAnimation                = Animation::New(8.0);
413     KeyFrames keyframes       = KeyFrames::New();
414     float     lengthAnimation = 0.25;
415     for(int32_t i = 0; i < 5; ++i)
416     {
417       keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
418     }
419     mAnimation.AnimateBetween(Property(mModelView, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
420     mAnimation.SetLooping(true);
421     mAnimation.Play();
422   }
423
424   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)
425   {
426     if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
427     {
428       DALI_LOG_ERROR("Can not crop outside of texture area.\n");
429       return nullptr;
430     }
431     uint32_t byteSize   = bytesPerPixel * xFaceSize * yFaceSize;
432     uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
433
434     int32_t srcStride  = width * bytesPerPixel;
435     int32_t destStride = xFaceSize * bytesPerPixel;
436     int32_t srcOffset  = xOffset * bytesPerPixel + yOffset * srcStride;
437     int32_t destOffset = 0;
438     for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
439     {
440       memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
441       srcOffset += srcStride;
442       destOffset += destStride;
443     }
444
445     return destBuffer;
446   }
447
448   void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
449   {
450     uint8_t* imageBuffer   = pixelBuffer.GetBuffer();
451     uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
452     uint32_t imageWidth    = pixelBuffer.GetWidth();
453     uint32_t imageHeight   = pixelBuffer.GetHeight();
454
455     int32_t faceSize = imageWidth / 4;
456
457     uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
458     uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
459
460     uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
461     if(tempImageBuffer)
462     {
463       PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
464       texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
465     }
466   }
467
468   void ChangeModel(int32_t direction)
469   {
470     mCurrentGlTF += direction;
471     if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
472     {
473       mCurrentGlTF = 0;
474     }
475     if(mCurrentGlTF < 0)
476     {
477       mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
478     }
479     CreateSceneFromGLTF(mCurrentGlTF);
480     SetAnimation();
481     mAnimationStop = false;
482     mWheelDelta    = 1.0f;
483   }
484
485   /**
486    * This function will change the material Roughness, Metalness or the model orientation when touched
487    */
488   bool OnTouch(Actor actor, const TouchEvent& touch)
489   {
490     const PointState::Type state = touch.GetState(0);
491
492     switch(state)
493     {
494       case PointState::DOWN:
495       {
496         if(mDoubleTap)
497         {
498           ChangeModel(1);
499         }
500         mDoubleTapTime.Stop();
501
502         mPointZ = touch.GetScreenPosition(0);
503         if(!mAnimationStop)
504         {
505           mAnimation.Pause();
506         }
507         break;
508       }
509
510       case PointState::MOTION:
511       {
512         const Size    size   = mWindow.GetSize();
513         const float   scaleX = size.width;
514         const float   scaleY = size.height;
515         const Vector2 point  = touch.GetScreenPosition(0);
516         mProcess             = false;
517         // If the touch is not processed above, then change the model orientation
518         if(!mProcess)
519         {
520           /**
521            * This is the motion for the swipe to rotate camera that targeting Vector3::ZERO.
522            * Each quaternion is used to rotate camera by Pitch and Yaw respectively.
523            * Final Orientation of camera is combination of Pitch and Yaw quaternion.
524            * For the natural rendering, Skybox also relocated at the new camera position.
525            */
526           mProcess           = true;
527           const float angle1 = ((mPointZ.y - point.y) / scaleY);
528           const float angle2 = ((mPointZ.x - point.x) / scaleX);
529
530           Quaternion pitch(Radian(Degree(angle1 * -200.0f)), (mCameraPosition.z >= 0) ? Vector3::XAXIS : -Vector3::XAXIS);
531           Quaternion yaw(Radian(Degree(angle2 * -200.0f)), -Vector3::YAXIS);
532
533           Quaternion newModelOrientation = yaw * pitch;
534           mCameraPosition                = newModelOrientation.Rotate(mCameraPosition);
535           mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
536           mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
537
538           mPointZ = point;
539         }
540         break;
541       }
542
543       case PointState::UP:
544       {
545         mDoubleTapTime.Start();
546         mDoubleTap = true;
547         if(!mAnimationStop)
548         {
549           mAnimation.Play();
550         }
551         mProcess = false;
552         break;
553       }
554
555       default:
556       {
557         break;
558       }
559     }
560     return true;
561   }
562
563   void OnKeyEvent(const KeyEvent& event)
564   {
565     if(event.GetState() == KeyEvent::DOWN)
566     {
567       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
568       {
569         mApplication.Quit();
570       }
571       if(event.GetKeyName() == "Down" ||
572          event.GetKeyName() == "Up")
573       {
574         if(!mProcess)
575         {
576           if(mAnimationStop)
577           {
578             mAnimation.Play();
579           }
580           else
581           {
582             mAnimation.Pause();
583           }
584           mAnimationStop = !mAnimationStop;
585         }
586       }
587
588       if(IsKey(event, Dali::DALI_KEY_CURSOR_RIGHT))
589       {
590         ChangeModel(1);
591       }
592
593       if(IsKey(event, Dali::DALI_KEY_CURSOR_LEFT))
594       {
595         ChangeModel(-1);
596       }
597     }
598   }
599
600 private:
601   Window       mWindow;
602   Layer        mLayer3D;
603   Application& mApplication;
604   CameraActor  mCameraActor;
605   Dali::Timer  mTimer;
606
607   Vector3   mCameraPosition;
608   Dali::Scene3D::ModelView mModelView;
609
610   Vector2    mPointZ;
611   Quaternion mModelOrientation;
612
613   Shader     mShaderSkybox;
614   Geometry   mSkyboxGeometry;
615   TextureSet mSkyboxTextures;
616   Renderer   mSkyboxRenderer;
617   Actor      mSkyboxActor;
618
619   Animation mAnimation;
620   bool      mProcess{false};
621   bool      mAnimationStop;
622
623   Timer mDoubleTapTime;
624   bool  mDoubleTap{false};
625
626   float mWheelDelta{1.0f};
627
628   int32_t mCurrentGlTF{0};
629 };
630
631 int32_t DALI_EXPORT_API main(int32_t argc, char** argv)
632 {
633   Application         application = Application::New(&argc, &argv);
634   Scene3DModelViewExample test(application);
635   application.MainLoop();
636   return 0;
637 }