[NUI.Scene3D] Deprecate direct accessor to motion value list
[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         mStaticMotionData.Add(
311             new MotionTransformIndex()
312             {
313                 ModelNodeId = new PropertyKey("Main"),
314                 TransformType = MotionTransformIndex.TransformTypes.Orientation,
315             },
316             new MotionValue()
317             {
318                 Value = new PropertyValue(new Rotation(new Radian(new Degree(-45.0f)), Vector3.ZAxis)),
319             }
320         );
321         mStaticRevertMotionData.Add(
322             new MotionTransformIndex()
323             {
324                 ModelNodeId = new PropertyKey("Main"),
325                 TransformType = MotionTransformIndex.TransformTypes.Orientation,
326             },
327             new MotionValue()
328             {
329                 Value = new PropertyValue(new Rotation(new Radian(new Degree(0.0f)), Vector3.ZAxis)),
330             }
331         );
332         mStaticRevertMotionData.Add(
333             new MotionTransformIndex()
334             {
335                 ModelNodeId = new PropertyKey("Main"),
336                 TransformType = MotionTransformIndex.TransformTypes.Scale,
337             },
338             new MotionValue()
339             {
340                 Value = new PropertyValue(Vector3.One),
341             }
342         );
343
344         mAnimateMotionData.Add(
345             new MotionTransformIndex()
346             {
347                 ModelNodeId = new PropertyKey("Main"),
348                 TransformType = MotionTransformIndex.TransformTypes.Scale,
349             },
350             new MotionValue()
351             {
352                 Value = new PropertyValue(new Vector3(0.5f, 1.5f, 1.0f)),
353             }
354         );
355         for (int i = 0; i < 8; ++i)
356         {
357             MotionIndex index = new BlendShapeIndex()
358             {
359                 ModelNodeId = new PropertyKey("Main"),
360                 BlendShapeId = new PropertyKey(i),
361             };
362             MotionValue value = new MotionValue()
363             {
364                 KeyFramesValue = new KeyFrames()
365             };
366             value.KeyFramesValue.Add(0.0f, 0.0f);
367             value.KeyFramesValue.Add(1.0f, 1.0f * ((float)Math.Abs(i - 3.5f) + 0.5f) / 4.0f);
368
369             mAnimateMotionData.Add(index, value);
370         }
371     }
372
373     void SetupIBLimage(string specularUrl, string diffuseUrl, float iblFactor)
374     {
375         mSceneView.SetImageBasedLightSource(IMAGE_DIR + specularUrl, IMAGE_DIR + diffuseUrl, iblFactor);
376
377         mMutex = true;
378     }
379
380     void OnKeyEvent(object source, Window.KeyEventArgs e)
381     {
382         // Skip interaction when some resources are changing now.
383         if (mMutex)
384         {
385             return;
386         }
387         if (e.Key.State == Key.StateType.Down)
388         {
389             switch (e.Key.KeyPressedName)
390             {
391                 case "Escape":
392                 case "Back":
393                 {
394                     Deactivate();
395                     Exit();
396                     break;
397                 }
398
399                 case "Return":
400                 case "Select":
401                 {
402                     currentCameraIndex++;
403                     if (currentCameraIndex >= mSceneView.GetCameraCount())
404                     {
405                         currentCameraIndex = 0;
406                     }
407
408                     Tizen.Log.Error("NUI", $"Use camera [{currentCameraIndex}]\n");
409                     mSceneView.CameraTransition(currentCameraIndex, cameraAnimationDurationMilliseconds);
410
411                     mMutex = true;
412                     break;
413                 }
414
415                 case "1":
416                 {
417                     currentModelIndex++;
418                     if (currentModelIndex >= ModelUrlList.Count)
419                     {
420                         currentModelIndex = 0;
421                     }
422
423                     Tizen.Log.Error("NUI", $"Create model [{currentModelIndex}]\n");
424                     CreateModel(ModelUrlList[currentModelIndex]);
425                     break;
426                 }
427                 case "2":
428                 {
429                     currentIBLIndex++;
430                     if (currentIBLIndex >= IBLUrlList.Count)
431                     {
432                         currentIBLIndex = 0;
433                     }
434
435                     Tizen.Log.Error("NUI", $"Use Light image [{currentIBLIndex}]\n");
436                     SetupIBLimage(IBLUrlList[currentIBLIndex].Item1, IBLUrlList[currentIBLIndex].Item2, IBLFactor);
437                     break;
438                 }
439                 case "r":
440                 {
441                     if (mModelRotateAnimation.State == Animation.States.Playing)
442                     {
443                         mModelRotateAnimation.Pause();
444                     }
445                     else
446                     {
447                         mModelRotateAnimation.Play();
448                     }
449                     break;
450                 }
451                 case "f":
452                 {
453                     if (mModelAnimation?.State == Animation.States.Playing)
454                     {
455                         if (mModel != null && mModelLoadFinished)
456                         {
457                             mMotionAnimation = mModel.GenerateMotionDataAnimation(mAnimateMotionData, modelMotionAnimationDurationMilliseconds);
458
459                             if (mMotionAnimation != null)
460                             {
461                                 // Stop original model animation
462                                 mModelAnimation.Stop();
463
464                                 mModel.SetMotionData(mStaticMotionData);
465                                 mMotionAnimation.Looping = true;
466                                 mMotionAnimation.BlendPoint = 0.25f;
467                                 mMotionAnimation.Play();
468                                 Tizen.Log.Error("NUI", $"Animate pre-defined motion data!\n");
469                             }
470                         }
471                     }
472                     break;
473                 }
474             }
475
476             FullGC();
477         }
478         else if (e.Key.State == Key.StateType.Up)
479         {
480             if (mModelAnimation?.State == Animation.States.Stopped)
481             {
482                 if (mMotionAnimation != null)
483                 {
484                     mMotionAnimation.Stop();
485                     mMotionAnimation.Dispose();
486                     mMotionAnimation = null;
487
488                     // Revert motion data
489                     mModel.SetMotionData(mStaticRevertMotionData);
490
491                     // Replay original model animation
492                     mModelAnimation.Play();
493                 }
494             }
495         }
496     }
497
498     public void Activate()
499     {
500         mWindow = Window.Instance;
501         mWindow.BackgroundColor = Color.DarkOrchid;
502         mWindowSize = mWindow.WindowSize;
503
504         mWindow.Resized += (o, e) =>
505         {
506             mWindowSize = mWindow.WindowSize;
507             mSceneView.Size = new Size(mWindowSize);
508         };
509
510         mWindow.KeyEvent += OnKeyEvent;
511
512         // Create motion data for MorphStressTest.gltf
513         CreateMotionData();
514
515         CreateSceneView();
516         SetupIBLimage(IBLUrlList[currentIBLIndex].Item1, IBLUrlList[currentIBLIndex].Item2, IBLFactor);
517         CreateModel(ModelUrlList[currentModelIndex]);
518     }
519     public void FullGC()
520     {
521         global::System.GC.Collect();
522         global::System.GC.WaitForPendingFinalizers();
523         global::System.GC.Collect();
524     }
525
526     public void Deactivate()
527     {
528         DestroyScene();
529     }
530     private void DestroyScene()
531     {
532     }
533
534     protected override void OnCreate()
535     {
536         // Up call to the Base class first
537         base.OnCreate();
538         Activate();
539     }
540
541     /// <summary>
542     /// The main entry point for the application.
543     /// </summary>
544     [STAThread] // Forces app to use one thread to access NUI
545     static void Main(string[] args)
546     {
547         Scene3DSample example = new Scene3DSample();
548         example.Run(args);
549     }
550 }