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