ProceduralImage.cs 11 KB

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