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