Merge remote-tracking branch 'origin/master' into tizen
[platform/core/csapi/tizenfx.git] / test / Tizen.NUI.Scene3D.Sample / Scene3DSample.cs
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 using System;
19 using Tizen.NUI;
20 using Tizen.NUI.BaseComponents;
21 using Tizen.NUI.Constants;
22 using Tizen.NUI.Scene3D;
23 using System.Collections.Generic;
24
25 class Scene3DSample : NUIApplication
26 {
27     static string IMAGE_DIR = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "image/";
28     static string MODEL_DIR = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "model/";
29
30     Window mWindow;
31     Vector2 mWindowSize;
32
33     SceneView mSceneView;
34     Model mModel;
35     Animation mModelAnimation;
36     bool mModelLoadFinished;
37
38     // Note : This motion data works well only if model is MorthStressTest!
39     MotionData mStaticMotionData;
40     MotionData mStaticRevertMotionData;
41     MotionData mAnimateMotionData;
42     Animation mMotionAnimation;
43     const int modelMotionAnimationDurationMilliseconds = 2000; // milliseconds
44
45     Animation mModelRotateAnimation;
46     const int modelRotateAnimationDurationMilliseconds = 10000; // milliseconds
47
48     private bool mMutex = false; // Lock key event during some transition / Change informations
49
50     #region Model list define
51     /*
52      * Copyright 2021 Analytical Graphics, Inc.
53      * CC-BY 4.0 https://creativecommons.org/licenses/by/4.0/
54      */
55     private static readonly List<string> ModelUrlList = new List<string>()
56     {
57         // Model reference : https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
58         // Get from KhronosGroup https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet
59         "DamagedHelmet/DamagedHelmet.gltf",
60
61         //Get from KhronosGroup https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MorphStressTest
62         "MorphStressTest/MorphStressTest.gltf",
63
64         // Get from KhronosGroup https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/2CylinderEngine
65         "2CylinderEngine/2CylinderEngine_e.gltf",
66
67         // Get from KhronosGroup https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/ToyCar
68         "ToyCar/ToyCar.glb",
69
70         //Get from KhronosGroup https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoxAnimated
71         "BoxAnimated/BoxAnimated.gltf",
72     };
73     private int currentModelIndex = 0;
74     #endregion
75
76     #region Image based light list define
77     private static readonly List<(string, string)> IBLUrlList = new List<(string, string)>
78     {
79         ("forest_diffuse_cubemap.png", "forest_specular_cubemap.png"),
80         ("papermill_E_diffuse-64.ktx", "papermill_pmrem.ktx"),
81         ("Irradiance.ktx", "Radiance.ktx"),
82     };
83     private int currentIBLIndex = 0;
84     private float IBLFactor = 0.7f;
85     #endregion
86
87     #region Camera list define
88     private class CameraInfo
89     {
90         public Vector3 Position { get; set; } = Vector3.Zero;
91         public Rotation Orientation { get; set; } = null;
92         public Radian Fov { get; set; } = null;
93         public float Near { get; set; } = 0.5f;
94         public float Far { get; set; } = 50.0f;
95     };
96     private static readonly List<CameraInfo> CameraInfoList = new List<CameraInfo>()
97     {
98         // -Z front and -Y up.
99         new CameraInfo(){
100             Position = new Vector3(0.0f, 0.0f, 5.55f),
101             // Basic camera           : +Z front and -Y up
102             // After YAxis 180 rotate : -Z front and -Y up
103             // Note : Default camera has YAxis 180.0f rotation even we didn't setup.
104             Orientation = new Rotation(new Radian(new Degree(180.0f)), Vector3.YAxis),
105             Fov = new Radian(new Degree(45.0f)),
106             Near = 0.5f,
107             Far = 50.0f,
108         },
109
110         // +Y front and +X up.
111         new CameraInfo(){
112             Position = new Vector3(0.0f, -3.95f, 0.0f),
113             // Rotate by XAxis first, and then rotate by YAxis
114             // Basic camera           : +Z front and -Y up
115             // After XAxis -90 rotate : +Y front and +Z up
116             // After YAxis 90 rotate  : +Y front and +X up
117             Orientation = new Rotation(new Radian(new Degree(90.0f)), Vector3.YAxis) *
118                           new Rotation(new Radian(new Degree(-90.0f)), Vector3.XAxis),
119             Fov = new Radian(new Degree(70.0f)),
120             Near = 0.5f,
121             Far = 50.0f,
122         },
123     };
124     uint currentCameraIndex = 0u;
125     List<Tizen.NUI.Scene3D.Camera> additionalCameraList;
126     const int cameraAnimationDurationMilliseconds = 2000; // milliseconds
127     #endregion
128
129     protected void CreateSceneView()
130     {
131         mSceneView = new SceneView()
132         {
133             SizeWidth = mWindowSize.Width,
134             SizeHeight = mWindowSize.Height,
135             PositionX = 0.0f,
136             PositionY = 0.0f,
137             PivotPoint = PivotPoint.TopLeft,
138             ParentOrigin = ParentOrigin.TopLeft,
139             PositionUsesPivotPoint = true,
140         };
141
142         mSceneView.CameraTransitionFinished += (o, e) =>
143         {
144             if (mMutex)
145             {
146                 mMutex = false;
147             }
148         };
149
150         mSceneView.ResourcesLoaded += (o, e) =>
151         {
152             Tizen.Log.Error("NUI", $"IBL image loaded done\n");
153             if (mMutex)
154             {
155                 mMutex = false;
156             }
157         };
158
159         SetupSceneViewCamera(mSceneView);
160
161         mWindow.Add(mSceneView);
162     }
163     private void SetupSceneViewCamera(SceneView sceneView)
164     {
165         int cameraCount = CameraInfoList.Count;
166         for (int i = 0; i < cameraCount; ++i)
167         {
168             var cameraInfo = CameraInfoList[i];
169             Tizen.NUI.Scene3D.Camera camera;
170             if (i == 0)
171             {
172                 // Default camera setting
173                 // Note : SceneView always have 1 default camera.
174                 camera = sceneView.GetCamera(0u);
175             }
176             else
177             {
178                 // Additional camera setting (top view camera).
179                 camera = new Tizen.NUI.Scene3D.Camera();
180                 sceneView.AddCamera(camera);
181             }
182             camera.PositionX = cameraInfo?.Position?.X ?? 0.0f;
183             camera.PositionY = cameraInfo?.Position?.Y ?? 0.0f;
184             camera.PositionZ = cameraInfo?.Position?.Z ?? 0.0f;
185             camera.Orientation = cameraInfo?.Orientation ?? new Rotation(new Radian(new Degree(180.0f)), Vector3.YAxis);
186
187             camera.NearPlaneDistance = cameraInfo?.Near ?? 0.5f;
188             camera.FarPlaneDistance = cameraInfo?.Far ?? 50.0f;
189             camera.FieldOfView = cameraInfo?.Fov ?? new Radian(new Degree(45.0f));
190         }
191     }
192
193     protected void CreateModel(string modelUrl)
194     {
195         // Release old one.
196         if (mModel != null)
197         {
198             mModel.Unparent();
199             mModel.Dispose();
200         }
201
202         // Release old animation if exist
203         if (mModelAnimation != null)
204         {
205             mModelAnimation.Stop();
206             mModelAnimation.Dispose();
207             mModelAnimation = null;
208         }
209
210         // Release old camera if exist
211         if (additionalCameraList != null)
212         {
213             if (currentCameraIndex >= CameraInfoList.Count)
214             {
215                 currentCameraIndex = 0u;
216                 Tizen.Log.Error("NUI", $"Use camera [{currentCameraIndex}]\n");
217                 mSceneView.SelectCamera(currentCameraIndex);
218             }
219             foreach (var additionalCamera in additionalCameraList)
220             {
221                 mSceneView.RemoveCamera(additionalCamera);
222                 additionalCamera.Dispose();
223             }
224             additionalCameraList = null;
225         }
226
227         mModelLoadFinished = false;
228
229         mModel = new Model(MODEL_DIR + modelUrl)
230         {
231             Name = modelUrl,
232         };
233         mModel.ResourcesLoaded += (s, e) =>
234         {
235             Model model = s as Model;
236
237             // You can play animation if the animation exists.
238             if (model.GetAnimationCount() > 0u)
239             {
240                 mModelAnimation = model.GetAnimation(0u);
241                 if (mModelAnimation != null)
242                 {
243                     mModelAnimation.Looping = true;
244                     mModelAnimation.Play();
245                 }
246             }
247             // You can apply camera properties if the camera parameter exists.
248             if (model.GetCameraCount() > 0u)
249             {
250                 additionalCameraList = new List<Tizen.NUI.Scene3D.Camera>();
251                 bool firstSucceededCamera = true;
252                 for (uint i = 0; i < model.GetCameraCount(); ++i)
253                 {
254                     Tizen.NUI.Scene3D.Camera additionalCamera = new Tizen.NUI.Scene3D.Camera();
255                     // If we success to make additional camera from model, Add that camera into sceneview, and select that.
256                     if (model.ApplyCamera(i, additionalCamera))
257                     {
258                         mSceneView.AddCamera(additionalCamera);
259                         if (firstSucceededCamera)
260                         {
261                             currentCameraIndex = mSceneView.GetCameraCount() - 1u;
262
263                             Tizen.Log.Error("NUI", $"Use additional camera [{currentCameraIndex}]\n");
264                             mSceneView.SelectCamera(currentCameraIndex);
265                             firstSucceededCamera = false;
266                         }
267                         additionalCameraList.Add(additionalCamera);
268                     }
269                     else
270                     {
271                         Tizen.Log.Error("NUI", $"Error! camera at [{i}] have some problem\n");
272                         additionalCamera.Dispose();
273                     }
274                 }
275             }
276             Tizen.Log.Error("NUI", $"{model.Name} size : {model.Size.Width}, {model.Size.Height}, {model.Size.Depth}\n");
277             Tizen.Log.Error("NUI", $"Animation count {model.GetAnimationCount()} , Camera count {model.GetCameraCount()}\n");
278
279             // Auto rotate model only if it don't have camera.
280             if (mModel.GetCameraCount() == 0u)
281             {
282                 mModelRotateAnimation.Play();
283             }
284
285             mModelLoadFinished = true;
286
287             if (mMutex)
288             {
289                 mMutex = false;
290             }
291         };
292
293         mModelRotateAnimation = new Animation(modelRotateAnimationDurationMilliseconds);
294         mModelRotateAnimation.AnimateBy(mModel, "Orientation", new Rotation(new Radian(new Degree(360.0f)), Vector3.YAxis));
295
296         mModelRotateAnimation.Looping = true;
297
298         mSceneView.Add(mModel);
299
300         mMutex = true;
301     }
302
303     // Note : This motion data works well only if model is MorthStressTest!
304     private void CreateMotionData()
305     {
306         mStaticMotionData = new MotionData();
307         mStaticRevertMotionData = new MotionData();
308         mAnimateMotionData = new MotionData();
309
310         mAnimateMotionData.Duration = modelMotionAnimationDurationMilliseconds;
311
312         mStaticMotionData.Add(
313             new MotionTransformIndex()
314             {
315                 ModelNodeId = new PropertyKey("Main"),
316                 TransformType = MotionTransformIndex.TransformTypes.Orientation,
317             },
318             new MotionValue()
319             {
320                 PropertyValue = new PropertyValue(new Rotation(new Radian(new Degree(-45.0f)), Vector3.ZAxis)),
321             }
322         );
323         mStaticRevertMotionData.Add(
324             new MotionTransformIndex()
325             {
326                 ModelNodeId = new PropertyKey("Main"),
327                 TransformType = MotionTransformIndex.TransformTypes.Orientation,
328             },
329             new MotionValue()
330             {
331                 PropertyValue = new PropertyValue(new Rotation(new Radian(new Degree(0.0f)), Vector3.ZAxis)),
332             }
333         );
334         mStaticRevertMotionData.Add(
335             new MotionTransformIndex()
336             {
337                 ModelNodeId = new PropertyKey("Main"),
338                 TransformType = MotionTransformIndex.TransformTypes.Scale,
339             },
340             new MotionValue()
341             {
342                 PropertyValue = new PropertyValue(Vector3.One),
343             }
344         );
345
346         mAnimateMotionData.Add(
347             new MotionTransformIndex()
348             {
349                 ModelNodeId = new PropertyKey("Main"),
350                 TransformType = MotionTransformIndex.TransformTypes.Scale,
351             },
352             new MotionValue()
353             {
354                 PropertyValue = new PropertyValue(new Vector3(0.5f, 1.5f, 1.0f)),
355             }
356         );
357         for (int i = 0; i < 8; ++i)
358         {
359             MotionIndex index = new BlendShapeIndex()
360             {
361                 ModelNodeId = new PropertyKey("Main"),
362                 BlendShapeId = new PropertyKey(i),
363             };
364             MotionValue value = new MotionValue()
365             {
366                 KeyFramesValue = new KeyFrames()
367             };
368             value.KeyFramesValue.Add(0.0f, 0.0f);
369             value.KeyFramesValue.Add(1.0f, 1.0f * ((float)Math.Abs(i - 3.5f) + 0.5f) / 4.0f);
370
371             mAnimateMotionData.Add(index, value);
372         }
373     }
374
375     void SetupIBLimage(string specularUrl, string diffuseUrl, float iblFactor)
376     {
377         mSceneView.SetImageBasedLightSource(IMAGE_DIR + specularUrl, IMAGE_DIR + diffuseUrl, iblFactor);
378
379         mMutex = true;
380     }
381
382     void OnKeyEvent(object source, Window.KeyEventArgs e)
383     {
384         // Skip interaction when some resources are changing now.
385         if (mMutex)
386         {
387             return;
388         }
389         if (e.Key.State == Key.StateType.Down)
390         {
391             switch (e.Key.KeyPressedName)
392             {
393                 case "Escape":
394                 case "Back":
395                 {
396                     Deactivate();
397                     Exit();
398                     break;
399                 }
400
401                 case "Return":
402                 case "Select":
403                 {
404                     currentCameraIndex++;
405                     if (currentCameraIndex >= mSceneView.GetCameraCount())
406                     {
407                         currentCameraIndex = 0;
408                     }
409
410                     Tizen.Log.Error("NUI", $"Use camera [{currentCameraIndex}]\n");
411                     mSceneView.CameraTransition(currentCameraIndex, cameraAnimationDurationMilliseconds);
412
413                     mMutex = true;
414                     break;
415                 }
416
417                 case "1":
418                 {
419                     currentModelIndex++;
420                     if (currentModelIndex >= ModelUrlList.Count)
421                     {
422                         currentModelIndex = 0;
423                     }
424
425                     Tizen.Log.Error("NUI", $"Create model [{currentModelIndex}]\n");
426                     CreateModel(ModelUrlList[currentModelIndex]);
427                     break;
428                 }
429                 case "2":
430                 {
431                     currentIBLIndex++;
432                     if (currentIBLIndex >= IBLUrlList.Count)
433                     {
434                         currentIBLIndex = 0;
435                     }
436
437                     Tizen.Log.Error("NUI", $"Use Light image [{currentIBLIndex}]\n");
438                     SetupIBLimage(IBLUrlList[currentIBLIndex].Item1, IBLUrlList[currentIBLIndex].Item2, IBLFactor);
439                     break;
440                 }
441                 case "r":
442                 {
443                     if (mModelRotateAnimation.State == Animation.States.Playing)
444                     {
445                         mModelRotateAnimation.Pause();
446                     }
447                     else
448                     {
449                         mModelRotateAnimation.Play();
450                     }
451                     break;
452                 }
453                 case "f":
454                 {
455                     if (mModelAnimation?.State == Animation.States.Playing)
456                     {
457                         if (mModel != null && mModelLoadFinished)
458                         {
459                             mMotionAnimation = mModel.GenerateMotionDataAnimation(mAnimateMotionData);
460
461                             if (mMotionAnimation != null)
462                             {
463                                 // Stop original model animation
464                                 mModelAnimation.Stop();
465
466                                 mModel.SetMotionData(mStaticMotionData);
467                                 mMotionAnimation.Looping = true;
468                                 mMotionAnimation.BlendPoint = 0.25f;
469                                 mMotionAnimation.Play();
470                                 Tizen.Log.Error("NUI", $"Animate pre-defined motion data!\n");
471                             }
472                         }
473                     }
474                     break;
475                 }
476             }
477
478             FullGC();
479         }
480         else if (e.Key.State == Key.StateType.Up)
481         {
482             if (mModelAnimation?.State == Animation.States.Stopped)
483             {
484                 if (mMotionAnimation != null)
485                 {
486                     mMotionAnimation.Stop();
487                     mMotionAnimation.Dispose();
488                     mMotionAnimation = null;
489
490                     // Revert motion data
491                     mModel.SetMotionData(mStaticRevertMotionData);
492
493                     // Replay original model animation
494                     mModelAnimation.Play();
495                 }
496             }
497         }
498     }
499
500     public void Activate()
501     {
502         mWindow = Window.Instance;
503         mWindow.BackgroundColor = Color.DarkOrchid;
504         mWindowSize = mWindow.WindowSize;
505
506         mWindow.Resized += (o, e) =>
507         {
508             mWindowSize = mWindow.WindowSize;
509             mSceneView.Size = new Size(mWindowSize);
510         };
511
512         mWindow.KeyEvent += OnKeyEvent;
513
514         // Create motion data for MorphStressTest.gltf
515         CreateMotionData();
516
517         CreateSceneView();
518         SetupIBLimage(IBLUrlList[currentIBLIndex].Item1, IBLUrlList[currentIBLIndex].Item2, IBLFactor);
519         CreateModel(ModelUrlList[currentModelIndex]);
520     }
521     public void FullGC()
522     {
523         global::System.GC.Collect();
524         global::System.GC.WaitForPendingFinalizers();
525         global::System.GC.Collect();
526     }
527
528     public void Deactivate()
529     {
530         DestroyScene();
531     }
532     private void DestroyScene()
533     {
534     }
535
536     protected override void OnCreate()
537     {
538         // Up call to the Base class first
539         base.OnCreate();
540         Activate();
541     }
542
543     /// <summary>
544     /// The main entry point for the application.
545     /// </summary>
546     [STAThread] // Forces app to use one thread to access NUI
547     static void Main(string[] args)
548     {
549         Scene3DSample example = new Scene3DSample();
550         example.Run(args);
551     }
552 }