FaceBuilder.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. using System.Collections;
  2. using TriLibCore.General;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. namespace TriLibCore.Samples
  6. {
  7. /// <summary>Represents a TriLib sample which allows the user to load models from a website and switch between them to build a 3D face.</summary>
  8. public class FaceBuilder : AssetViewerBase
  9. {
  10. /// <summary>
  11. /// Parts turning interval in seconds.
  12. /// </summary>
  13. private const float TurnLength = 1f;
  14. /// <summary>
  15. /// Distance face parts should stay from the scene center.
  16. /// </summary>
  17. private const float Distance = 100f;
  18. /// <summary>
  19. /// URI from where the models will be downloaded.
  20. /// </summary>
  21. private const string BaseURI = "https://ricardoreis.net/trilib/demos/assetdownloader/";
  22. /// <summary>
  23. /// Downloaded item indicator template.
  24. /// </summary>
  25. [SerializeField]
  26. private GameObject _downloadTemplate;
  27. /// <summary>
  28. /// Switches to the previous part.
  29. /// </summary>
  30. /// <param name="partName">The area name to switch (hair, eyes, nose or mouth).</param>
  31. public void PreviousPart(string partName)
  32. {
  33. StartCoroutine(TurnWrapper(partName, -90f));
  34. }
  35. /// <summary>
  36. /// Switches to the next part.
  37. /// </summary>
  38. /// <param name="partName">The area name to switch (hair, eyes, nose or mouth).</param>
  39. public void NextPart(string partName)
  40. {
  41. StartCoroutine(TurnWrapper(partName, 90f));
  42. }
  43. /// <summary>
  44. /// Coroutine used to actually switch/turn the parts.
  45. /// </summary>
  46. /// <param name="partName">The area name to switch (hair, eyes, nose or mouth).</param>
  47. /// <param name="angle">The relative angle to rotate the part.</param>
  48. /// <returns>The Coroutine IEnumerator.</returns>
  49. private IEnumerator TurnWrapper(string partName, float angle)
  50. {
  51. var wrapper = GameObject.Find($"{partName}Wrapper");
  52. if (wrapper == null)
  53. {
  54. yield break;
  55. }
  56. var line = GameObject.Find($"{partName}Line");
  57. if (line == null)
  58. {
  59. yield break;
  60. }
  61. var buttons = line.GetComponentsInChildren<Button>();
  62. foreach (var button in buttons)
  63. {
  64. button.enabled = false;
  65. }
  66. var initialYaw = wrapper.transform.rotation.eulerAngles.y;
  67. var finalYaw = initialYaw + angle;
  68. for (var i = 0f; i < TurnLength; i += Time.deltaTime)
  69. {
  70. var eulerAngles = wrapper.transform.rotation.eulerAngles;
  71. eulerAngles.y = Mathf.Lerp(initialYaw, finalYaw, Easing(i / TurnLength));
  72. wrapper.transform.rotation = Quaternion.Euler(eulerAngles);
  73. yield return null;
  74. }
  75. var finalEulerAngles = wrapper.transform.rotation.eulerAngles;
  76. finalEulerAngles.y = finalYaw;
  77. wrapper.transform.rotation = Quaternion.Euler(finalEulerAngles);
  78. foreach (var button in buttons)
  79. {
  80. button.enabled = true;
  81. }
  82. }
  83. /// <summary>Easing method to smooth out a rotation.</summary>
  84. /// <param name="value">The value to ease.</param>
  85. /// <returns>The smoothed-out value.</returns>
  86. private static float Easing(float value)
  87. {
  88. if ((value *= 2f) < 1f) return 0.5f * value * value * value;
  89. return 0.5f * ((value -= 2f) * value * value + 2f);
  90. }
  91. /// <summary>
  92. /// Loads all parts from the given area.
  93. /// </summary>
  94. /// <param name="partName">The area name to load (hair, eyes, nose or mouth).</param>
  95. private void LoadParts(string partName)
  96. {
  97. for (var i = 0; i < 4; i++)
  98. {
  99. LoadPart(partName, i);
  100. }
  101. }
  102. /// <summary>
  103. /// Loads the part with the given index from the given area.
  104. /// </summary>
  105. /// <param name="partName">The area name to load (hair, eyes, nose or mouth).</param>
  106. /// <param name="partIndex">The area part index.</param>
  107. private void LoadPart(string partName, int partIndex)
  108. {
  109. var wrapper = GameObject.Find($"{partName}Wrapper");
  110. if (wrapper == null)
  111. {
  112. return;
  113. }
  114. var request = AssetDownloader.CreateWebRequest($"{BaseURI}{partName}{partIndex}.zip");
  115. AssetDownloader.LoadModelFromUri(request, OnLoad, OnMaterialsLoad, OnProgress, OnError, wrapper, AssetLoaderOptions, partIndex, null, true);
  116. }
  117. /// <summary>Checks if the Dispatcher instance exists, stores this class instance as the Singleton and load all area parts.</summary>
  118. protected override void Start()
  119. {
  120. base.Start();
  121. AssetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
  122. LoadParts("Hair");
  123. LoadParts("Eyes");
  124. LoadParts("Nose");
  125. LoadParts("Mouth");
  126. }
  127. /// <summary>Event triggered when there is any Model loading error.</summary>
  128. /// <param name="contextualizedError">The Contextualized Error that has occurred.</param>
  129. protected override void OnError(IContextualizedError contextualizedError)
  130. {
  131. var context = contextualizedError.GetContext();
  132. if (context is AssetLoaderContext assetLoaderContext)
  133. {
  134. var zipLoadCustomContextData = (ZipLoadCustomContextData)assetLoaderContext.CustomData;
  135. var uriLoadCustomContextData = (UriLoadCustomContextData)zipLoadCustomContextData.CustomData;
  136. var downloaded = Instantiate(_downloadTemplate, _downloadTemplate.transform.parent);
  137. var text = downloaded.GetComponentInChildren<Text>();
  138. text.text = $"Error: {uriLoadCustomContextData.UnityWebRequest.uri.Segments[uriLoadCustomContextData.UnityWebRequest.uri.Segments.Length - 1]}";
  139. downloaded.SetActive(true);
  140. }
  141. base.OnError(contextualizedError);
  142. }
  143. /// <summary>Event triggered when the Model and all its resources loaded.</summary>
  144. /// <param name="assetLoaderContext">The Asset Loader Context reference. Asset Loader Context contains the information used during the Model loading process, which is available to almost every Model processing method</param>
  145. protected override void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
  146. {
  147. if (assetLoaderContext.RootGameObject != null)
  148. {
  149. var zipLoadCustomContextData = (ZipLoadCustomContextData)assetLoaderContext.CustomData;
  150. var uriLoadCustomContextData = (UriLoadCustomContextData)zipLoadCustomContextData.CustomData;
  151. var downloaded = Instantiate<GameObject>(_downloadTemplate, _downloadTemplate.transform.parent);
  152. var text = downloaded.GetComponentInChildren<Text>();
  153. text.text = $"Done: {uriLoadCustomContextData.UnityWebRequest.uri.Segments[uriLoadCustomContextData.UnityWebRequest.uri.Segments.Length - 1]}";
  154. downloaded.SetActive(true);
  155. }
  156. base.OnMaterialsLoad(assetLoaderContext);
  157. }
  158. /// <summary>Event triggered when the Model Meshes and hierarchy are loaded.</summary>
  159. /// <param name="assetLoaderContext">The Asset Loader Context reference. Asset Loader Context contains the information used during the Model loading process, which is available to almost every Model processing method</param>
  160. protected override void OnLoad(AssetLoaderContext assetLoaderContext)
  161. {
  162. if (assetLoaderContext.RootGameObject != null)
  163. {
  164. var zipLoadCustomContextData = (ZipLoadCustomContextData)assetLoaderContext.CustomData;
  165. var uriLoadCustomContextData = (UriLoadCustomContextData)zipLoadCustomContextData.CustomData;
  166. var partIndex = (int)uriLoadCustomContextData.CustomData;
  167. var rotation = Quaternion.Euler(0f, partIndex * 90f, 0f);
  168. assetLoaderContext.RootGameObject.transform.SetPositionAndRotation(rotation * new Vector3(0f, 0f, Distance), rotation);
  169. }
  170. base.OnLoad(assetLoaderContext);
  171. }
  172. }
  173. }