ProceduralImage.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.Sprites;
  4. using UnityEngine.UI;
  5. /* Author: Josh H.
  6. * Procedural UI Image
  7. * assetstore.joshh@gmail.com for feedback or questions
  8. */
  9. namespace UnityEngine.UI {
  10. [ExecuteInEditMode]
  11. [AddComponentMenu("UI/Procedural Image")]
  12. public class ProceduralImage : ImageProxy {
  13. [SerializeField]private float borderWidth;
  14. private ProceduralImageModifier modifier;
  15. public Material materialInstance;
  16. public Material materialInstanceClip;
  17. [SerializeField]private float falloffDistance = 1;
  18. public bool needClipping = false;
  19. public float BorderWidth {
  20. get {
  21. return borderWidth;
  22. }
  23. set {
  24. borderWidth = value;
  25. this.SetMaterialDirty();
  26. }
  27. }
  28. public float FalloffDistance {
  29. get {
  30. return falloffDistance;
  31. }
  32. set {
  33. falloffDistance = value;
  34. this.SetMaterialDirty();
  35. }
  36. }
  37. protected ProceduralImageModifier Modifier {
  38. get {
  39. if (modifier == null) {
  40. //try to get the modifier on the object.
  41. modifier = this.GetComponent<ProceduralImageModifier>();
  42. //if we did not find any modifier
  43. if(modifier == null){
  44. //Add free modifier
  45. ModifierType = typeof(FreeModifier);
  46. }
  47. }
  48. return modifier;
  49. }
  50. set{
  51. modifier = value;
  52. }
  53. }
  54. /// <summary>
  55. /// Gets or sets the type of the modifier. Adds a modifier of that type.
  56. /// </summary>
  57. /// <value>The type of the modifier.</value>
  58. public System.Type ModifierType {
  59. get {
  60. return Modifier.GetType();
  61. }
  62. set {
  63. if(this.GetComponent<ProceduralImageModifier>()!=null){
  64. Destroy(this.GetComponent<ProceduralImageModifier>());
  65. }
  66. this.gameObject.AddComponent(value);
  67. Modifier = this.GetComponent<ProceduralImageModifier>();
  68. this.SetAllDirty();
  69. }
  70. }
  71. override protected void OnEnable()
  72. {
  73. base.OnEnable ();
  74. // ScenceMain.MainUpdateEvent += MainUpdate;
  75. this.Init ();
  76. }
  77. /// <summary>
  78. /// Initializes this instance.
  79. /// </summary>
  80. public void Init (){
  81. // materialInstance = Image.defaultGraphicMaterial;
  82. // materialInstanceClip = Image.defaultETC1GraphicMaterial;
  83. // this.material = materialInstance;
  84. }
  85. protected override void OnDisable()
  86. {
  87. base.OnDisable();
  88. // ScenceMain.MainUpdateEvent -= MainUpdate;
  89. }
  90. public void Update(){
  91. if (needClipping) {
  92. // this.material = materialInstanceClip;
  93. } else {
  94. //this.material = materialInstance;
  95. }
  96. this.UpdateMaterial ();
  97. }
  98. /// <summary>
  99. /// Prevents radius to get bigger than rect size
  100. /// </summary>
  101. /// <returns>The fixed radius.</returns>
  102. /// <param name="vec">border-radius as Vector4 (starting upper-left, clockwise)</param>
  103. private Vector4 FixRadius(Vector4 vec){
  104. Rect r = this.rectTransform.rect;
  105. vec = new Vector4 (Mathf.Max(vec.x,0),Mathf.Max(vec.y,0),Mathf.Max(vec.z,0),Mathf.Max(vec.w,0));
  106. //float maxRadiusSums = Mathf.Max (vec.x,vec.z) + Mathf.Max (vec.y,vec.w);
  107. float scaleFactor = Mathf.Min(r.width/(vec.x+vec.y),r.width/(vec.z+vec.w),r.height/(vec.x+vec.w),r.height/(vec.z+vec.y),1);
  108. return vec*scaleFactor;
  109. }
  110. protected override void OnPopulateMesh(VertexHelper toFill)
  111. {
  112. //note: Sliced and Tiled have no effect to this currently.
  113. if (overrideSprite == null)
  114. {
  115. base.OnPopulateMesh(toFill);
  116. return;
  117. }
  118. switch (type)
  119. {
  120. case Type.Simple:
  121. GenerateSimpleSprite(toFill);
  122. break;
  123. case Type.Sliced:
  124. GenerateSimpleSprite(toFill);
  125. break;
  126. case Type.Tiled:
  127. GenerateSimpleSprite(toFill);
  128. break;
  129. case Type.Filled:
  130. base.OnPopulateMesh(toFill);
  131. break;
  132. }
  133. }
  134. #if UNITY_EDITOR
  135. protected override void Reset (){
  136. base.Reset ();
  137. OnEnable ();
  138. }
  139. #endif
  140. private Vector4 GetDrawingDimensions(bool shouldPreserveAspect)
  141. {
  142. var padding = overrideSprite == null ? Vector4.zero : DataUtility.GetPadding(overrideSprite);
  143. Rect r = GetPixelAdjustedRect();
  144. var size = overrideSprite == null ? new Vector2(r.width, r.height) : new Vector2(overrideSprite.rect.width, overrideSprite.rect.height);
  145. //Debug.Log(string.Format("r:{2}, size:{0}, padding:{1}", size, padding, r));
  146. int spriteW = Mathf.RoundToInt(size.x);
  147. int spriteH = Mathf.RoundToInt(size.y);
  148. if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
  149. {
  150. var spriteRatio = size.x / size.y;
  151. var rectRatio = r.width / r.height;
  152. if (spriteRatio > rectRatio)
  153. {
  154. var oldHeight = r.height;
  155. r.height = r.width * (1.0f / spriteRatio);
  156. r.y += (oldHeight - r.height) * rectTransform.pivot.y;
  157. }
  158. else
  159. {
  160. var oldWidth = r.width;
  161. r.width = r.height * spriteRatio;
  162. r.x += (oldWidth - r.width) * rectTransform.pivot.x;
  163. }
  164. }
  165. var v = new Vector4(
  166. padding.x / spriteW,
  167. padding.y / spriteH,
  168. (spriteW - padding.z) / spriteW,
  169. (spriteH - padding.w) / spriteH);
  170. v = new Vector4(
  171. r.x + r.width * v.x,
  172. r.y + r.height * v.y,
  173. r.x + r.width * v.z,
  174. r.y + r.height * v.w
  175. );
  176. return v;
  177. }
  178. //每个角最大的三角形数,一般5-8个就有不错的圆角效果,设置Max防止不必要的性能浪费
  179. const int MaxTriangleNum = 20;
  180. const int MinTriangleNum = 1;
  181. public float Radius=10;
  182. //使用几个三角形去填充每个角的四分之一圆
  183. [Range(MinTriangleNum, MaxTriangleNum)]
  184. public int TriangleNum=6;
  185. /// <summary>
  186. /// Generates the Verticies needed.
  187. /// </summary>
  188. /// <param name="vh">vertex helper</param>
  189. void GenerateSimpleSprite(VertexHelper vh){
  190. Vector4 v = GetDrawingDimensions(false);
  191. Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;
  192. var color32 = color;
  193. vh.Clear();
  194. //对radius的值做限制,必须在0-较小的边的1/2的范围内
  195. float radius = Radius;
  196. if (radius > (v.z - v.x) / 2) radius = (v.z - v.x) / 2;
  197. if (radius > (v.w - v.y) / 2) radius = (v.w - v.y) / 2;
  198. if (radius < 0) radius = 0;
  199. //计算出uv中对应的半径值坐标轴的半径
  200. float uvRadiusX = radius / (v.z - v.x);
  201. float uvRadiusY = radius / (v.w - v.y);
  202. //0,1
  203. vh.AddVert(new Vector3(v.x, v.w - radius), color32, new Vector2(uv.x, uv.w - uvRadiusY));
  204. vh.AddVert(new Vector3(v.x, v.y + radius), color32, new Vector2(uv.x, uv.y + uvRadiusY));
  205. //2,3,4,5
  206. vh.AddVert(new Vector3(v.x + radius, v.w), color32, new Vector2(uv.x + uvRadiusX, uv.w));
  207. vh.AddVert(new Vector3(v.x + radius, v.w - radius), color32, new Vector2(uv.x + uvRadiusX, uv.w - uvRadiusY));
  208. vh.AddVert(new Vector3(v.x + radius, v.y + radius), color32, new Vector2(uv.x + uvRadiusX, uv.y + uvRadiusY));
  209. vh.AddVert(new Vector3(v.x + radius, v.y), color32, new Vector2(uv.x + uvRadiusX, uv.y));
  210. //6,7,8,9
  211. vh.AddVert(new Vector3(v.z - radius, v.w), color32, new Vector2(uv.z - uvRadiusX, uv.w));
  212. vh.AddVert(new Vector3(v.z - radius, v.w - radius), color32, new Vector2(uv.z - uvRadiusX, uv.w - uvRadiusY));
  213. vh.AddVert(new Vector3(v.z - radius, v.y + radius), color32, new Vector2(uv.z - uvRadiusX, uv.y + uvRadiusY));
  214. vh.AddVert(new Vector3(v.z - radius, v.y), color32, new Vector2(uv.z - uvRadiusX, uv.y));
  215. //10,11
  216. vh.AddVert(new Vector3(v.z, v.w - radius), color32, new Vector2(uv.z, uv.w - uvRadiusY));
  217. vh.AddVert(new Vector3(v.z, v.y + radius), color32, new Vector2(uv.z, uv.y + uvRadiusY));
  218. //左边的矩形
  219. vh.AddTriangle(1, 0, 3);
  220. vh.AddTriangle(1, 4, 3);
  221. //中间的矩形
  222. vh.AddTriangle(5, 2, 6);
  223. vh.AddTriangle(5, 9, 6);
  224. //右边的矩形
  225. vh.AddTriangle(8, 7, 10);
  226. vh.AddTriangle(8, 11, 10);
  227. //开始构造四个角
  228. List<Vector2> vCenterList = new List<Vector2>();
  229. List<Vector2> uvCenterList = new List<Vector2>();
  230. List<int> vCenterVertList = new List<int>();
  231. //右上角的圆心
  232. vCenterList.Add(new Vector2(v.z - radius, v.w - radius));
  233. uvCenterList.Add(new Vector2(uv.z - uvRadiusX, uv.w - uvRadiusY));
  234. vCenterVertList.Add(7);
  235. //左上角的圆心
  236. vCenterList.Add(new Vector2(v.x + radius, v.w - radius));
  237. uvCenterList.Add(new Vector2(uv.x + uvRadiusX, uv.w - uvRadiusY));
  238. vCenterVertList.Add(3);
  239. //左下角的圆心
  240. vCenterList.Add(new Vector2(v.x + radius, v.y + radius));
  241. uvCenterList.Add(new Vector2(uv.x + uvRadiusX, uv.y + uvRadiusY));
  242. vCenterVertList.Add(4);
  243. //右下角的圆心
  244. vCenterList.Add(new Vector2(v.z - radius, v.y + radius));
  245. uvCenterList.Add(new Vector2(uv.z - uvRadiusX, uv.y + uvRadiusY));
  246. vCenterVertList.Add(8);
  247. //每个三角形的顶角
  248. float degreeDelta = (float)(Mathf.PI / 2 / TriangleNum);
  249. //当前的角度
  250. float curDegree = 0;
  251. for (int i = 0; i < vCenterVertList.Count; i++)
  252. {
  253. int preVertNum = vh.currentVertCount;
  254. for (int j = 0; j <= TriangleNum; j++)
  255. {
  256. float cosA = Mathf.Cos(curDegree);
  257. float sinA = Mathf.Sin(curDegree);
  258. Vector3 vPosition = new Vector3(vCenterList[i].x + cosA * radius, vCenterList[i].y + sinA * radius);
  259. Vector3 uvPosition = new Vector2(uvCenterList[i].x + cosA * uvRadiusX, uvCenterList[i].y + sinA * uvRadiusY);
  260. vh.AddVert(vPosition, color32, uvPosition);
  261. curDegree += degreeDelta;
  262. }
  263. curDegree -= degreeDelta;
  264. for (int j = 0; j <= TriangleNum - 1; j++)
  265. {
  266. vh.AddTriangle(vCenterVertList[i], preVertNum + j + 1, preVertNum + j);
  267. }
  268. }
  269. }
  270. /// <summary>
  271. /// Sets the material values of shader.
  272. /// Implementation of IMaterialModifier
  273. /// </summary>
  274. public override Material GetModifiedMaterial (Material baseMaterial){
  275. return base.GetModifiedMaterial(baseMaterial);
  276. Rect rect = this.GetComponent<RectTransform> ().rect;
  277. //get world-space corners of rect
  278. Vector3[] corners = new Vector3[4];
  279. rectTransform.GetWorldCorners (corners);
  280. float pixelSize = Vector3.Distance (corners [1], corners [2]) / rect.width;
  281. pixelSize = pixelSize/falloffDistance;
  282. Vector4 radius = FixRadius (Modifier.CalculateRadius (rect));
  283. Material m = MaterialHelper.SetMaterialValues (new ProceduralImageMaterialInfo(rect.width+falloffDistance,rect.height+falloffDistance,1,radius,Mathf.Max(borderWidth,0)),baseMaterial);
  284. return base.GetModifiedMaterial (m);
  285. }
  286. }
  287. }