CanvasCylinder.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. using System;
  2. using UnityEngine.Assertions;
  3. using System.Collections.Generic;
  4. using UnityEditor;
  5. using UnityEngine;
  6. namespace Rokid.UXR.Interaction
  7. {
  8. public class CanvasCylinder : CanvasMesh, ICurvedPlane
  9. {
  10. [Serializable]
  11. public struct MeshGenerationSettings
  12. {
  13. [Delayed]
  14. public float VerticesPerDegree;
  15. [Delayed]
  16. public int MaxHorizontalResolution;
  17. [Delayed]
  18. public int MaxVerticalResolution;
  19. }
  20. public const int MIN_RESOLUTION = 2;
  21. [SerializeField]
  22. [Tooltip("The cylinder used to dictate the position and radius of the mesh.")]
  23. private Cylinder _cylinder;
  24. [SerializeField]
  25. [Tooltip("Determines how the mesh is projected on the cylinder wall. " +
  26. "Vertical results in a left-to-right curvature, Horizontal results in a top-to-bottom curvature.")]
  27. private CylinderOrientation _orientation = CylinderOrientation.Vertical;
  28. [SerializeField]
  29. private MeshGenerationSettings _meshGeneration = new MeshGenerationSettings()
  30. {
  31. VerticesPerDegree = 1.4f,
  32. MaxHorizontalResolution = 128,
  33. MaxVerticalResolution = 32
  34. };
  35. public float Radius => _cylinder.Radius;
  36. public Cylinder Cylinder => _cylinder;
  37. public float ArcDegrees { get; private set; }
  38. public float Rotation { get; private set; }
  39. public float Bottom { get; private set; }
  40. public float Top { get; private set; }
  41. private float CylinderRelativeScale => _cylinder.transform.lossyScale.x / transform.lossyScale.x;
  42. protected override void Start()
  43. {
  44. Assert.IsNotNull(_cylinder);
  45. }
  46. #if UNITY_EDITOR
  47. protected virtual void OnValidate()
  48. {
  49. _meshGeneration.MaxHorizontalResolution = Mathf.Max(MIN_RESOLUTION,
  50. _meshGeneration.MaxHorizontalResolution);
  51. _meshGeneration.MaxVerticalResolution = Mathf.Max(MIN_RESOLUTION,
  52. _meshGeneration.MaxVerticalResolution);
  53. _meshGeneration.VerticesPerDegree = Mathf.Max(0, _meshGeneration.VerticesPerDegree);
  54. if (Application.isPlaying)
  55. {
  56. EditorApplication.delayCall += () =>
  57. {
  58. UpdateImposter();
  59. };
  60. }
  61. }
  62. #endif
  63. public override void UpdateImposter()
  64. {
  65. base.UpdateImposter();
  66. UpdateMeshPosition();
  67. UpdateCurvedPlane();
  68. }
  69. protected override Vector3 MeshInverseTransform(Vector3 localPosition)
  70. {
  71. float angle = Mathf.Atan2(localPosition.x / _cylinder.CylinderCanvasScaleWidth, localPosition.z + Radius);
  72. float x = angle * Radius;
  73. float y = localPosition.y / _cylinder.CylinderCanvasScaleHeight;
  74. return new Vector3(x, y);
  75. }
  76. protected override void GenerateMesh(out List<Vector3> verts,
  77. out List<int> tris,
  78. out List<Vector2> uvs)
  79. {
  80. verts = new List<Vector3>();
  81. tris = new List<int>();
  82. uvs = new List<Vector2>();
  83. Vector2 worldSize = GetWorldSize();
  84. float scaledRadius = Radius * CylinderRelativeScale;
  85. float xPos = worldSize.x * 0.5f;
  86. float xNeg = -xPos;
  87. float yPos = worldSize.y * 0.5f;
  88. float yNeg = -yPos;
  89. Vector2Int GetClampedResolution(float arcMax, float axisMax)
  90. {
  91. int horizontalResolution = Mathf.Max(2,
  92. Mathf.RoundToInt(_meshGeneration.VerticesPerDegree *
  93. Mathf.Rad2Deg * arcMax / scaledRadius));
  94. int verticalResolution =
  95. Mathf.Max(2, Mathf.RoundToInt(horizontalResolution * axisMax / arcMax));
  96. horizontalResolution = Mathf.Clamp(horizontalResolution, 2,
  97. _meshGeneration.MaxHorizontalResolution);
  98. verticalResolution = Mathf.Clamp(verticalResolution, 2,
  99. _meshGeneration.MaxVerticalResolution);
  100. return new Vector2Int(horizontalResolution, verticalResolution);
  101. }
  102. Vector3 GetCurvedPoint(float u, float v)
  103. {
  104. float x = Mathf.Lerp(xNeg, xPos, u);
  105. float y = Mathf.Lerp(yNeg, yPos, v);
  106. float angle;
  107. Vector3 point;
  108. switch (_orientation)
  109. {
  110. default:
  111. case CylinderOrientation.Vertical:
  112. angle = x / scaledRadius;
  113. point.x = Mathf.Sin(angle) * scaledRadius;
  114. point.y = y;
  115. point.z = Mathf.Cos(angle) * scaledRadius - scaledRadius;
  116. break;
  117. case CylinderOrientation.Horizontal:
  118. angle = y / scaledRadius;
  119. point.x = x;
  120. point.y = Mathf.Sin(angle) * scaledRadius;
  121. point.z = Mathf.Cos(angle) * scaledRadius - scaledRadius;
  122. break;
  123. }
  124. return point;
  125. }
  126. Vector2Int resolution;
  127. switch (_orientation)
  128. {
  129. default:
  130. case CylinderOrientation.Vertical:
  131. resolution = GetClampedResolution(xPos, yPos);
  132. break;
  133. case CylinderOrientation.Horizontal:
  134. resolution = GetClampedResolution(yPos, xPos);
  135. break;
  136. }
  137. for (int y = 0; y < resolution.y; y++)
  138. {
  139. for (int x = 0; x < resolution.x; x++)
  140. {
  141. float u = x / (resolution.x - 1.0f);
  142. float v = y / (resolution.y - 1.0f);
  143. verts.Add(GetCurvedPoint(u, v));
  144. uvs.Add(new Vector2(u, v));
  145. }
  146. }
  147. for (int y = 0; y < resolution.y - 1; y++)
  148. {
  149. for (int x = 0; x < resolution.x - 1; x++)
  150. {
  151. int v00 = x + y * resolution.x;
  152. int v10 = v00 + 1;
  153. int v01 = v00 + resolution.x;
  154. int v11 = v00 + 1 + resolution.x;
  155. tris.Add(v00);
  156. tris.Add(v11);
  157. tris.Add(v10);
  158. tris.Add(v00);
  159. tris.Add(v01);
  160. tris.Add(v11);
  161. }
  162. }
  163. }
  164. private void UpdateMeshPosition()
  165. {
  166. Vector3 posInCylinder = _cylinder.transform.InverseTransformPoint(transform.position);
  167. Vector3 localYOffset = new Vector3(0, posInCylinder.y, 0);
  168. Vector3 localCancelY = posInCylinder - localYOffset;
  169. // If canvas position is on cylinder center axis, project forward.
  170. // Otherwise, project canvas onto cylinder wall from center axis.
  171. Vector3 projection = Mathf.Approximately(localCancelY.sqrMagnitude, 0f) ?
  172. Vector3.forward : localCancelY.normalized;
  173. Vector3 localUp;
  174. switch (_orientation)
  175. {
  176. default:
  177. case CylinderOrientation.Vertical:
  178. localUp = Vector3.up;
  179. break;
  180. case CylinderOrientation.Horizontal:
  181. localUp = Vector3.right;
  182. break;
  183. }
  184. transform.position = _cylinder.transform.TransformPoint((projection * _cylinder.Radius) + localYOffset);
  185. transform.rotation = _cylinder.transform.rotation * Quaternion.LookRotation(projection, localUp);
  186. if (_meshCollider != null &&
  187. _meshCollider.transform != transform &&
  188. !transform.IsChildOf(_meshCollider.transform))
  189. {
  190. _meshCollider.transform.position = transform.position;
  191. _meshCollider.transform.rotation = transform.rotation;
  192. _meshCollider.transform.localScale *= transform.lossyScale.x / _meshCollider.transform.lossyScale.x;
  193. }
  194. }
  195. private Vector2 GetWorldSize()
  196. {
  197. Vector2Int resolution = _canvasRenderTexture.GetBaseResolutionToUse();
  198. float width = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.x)) * _cylinder.CylinderCanvasScaleWidth;
  199. float height = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.y)) * _cylinder.CylinderCanvasScaleHeight;
  200. return new Vector2(width, height) / transform.lossyScale;
  201. }
  202. private void UpdateCurvedPlane()
  203. {
  204. // Get world size in cylinder space
  205. Vector2 cylinderSize = GetWorldSize() / CylinderRelativeScale;
  206. float arcSize, axisSize;
  207. switch (_orientation)
  208. {
  209. default:
  210. case CylinderOrientation.Vertical:
  211. arcSize = cylinderSize.x;
  212. axisSize = cylinderSize.y;
  213. break;
  214. case CylinderOrientation.Horizontal:
  215. arcSize = cylinderSize.y;
  216. axisSize = cylinderSize.x;
  217. break;
  218. }
  219. Vector3 posInCylinder = Cylinder.transform.InverseTransformPoint(transform.position);
  220. Rotation = Mathf.Atan2(posInCylinder.x, posInCylinder.z) * Mathf.Rad2Deg;
  221. ArcDegrees = (arcSize * 0.5f / Radius) * 2f * Mathf.Rad2Deg;
  222. Top = posInCylinder.y + (axisSize * 0.5f);
  223. Bottom = posInCylinder.y - (axisSize * 0.5f);
  224. }
  225. }
  226. }