Path.cginc 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #if PATH_1 || FILLED_PATH_1
  2. #define MAX_SEGMENTS 1
  3. #elif PATH_2 || FILLED_PATH_2
  4. #define MAX_SEGMENTS 2
  5. #elif PATH_4 || FILLED_PATH_4
  6. #define MAX_SEGMENTS 4
  7. #elif PATH_8 || FILLED_PATH_8
  8. #define MAX_SEGMENTS 8
  9. #elif PATH_16 || FILLED_PATH_16
  10. #define MAX_SEGMENTS 16
  11. #elif PATH_24 || FILLED_PATH_24
  12. #define MAX_SEGMENTS 24
  13. #elif PATH_32 || FILLED_PATH_32
  14. #define MAX_SEGMENTS 32
  15. #endif
  16. #define MAX_POINTS 3 * MAX_SEGMENTS
  17. // each point is (x, y, is-in-loop, unused)
  18. float4 _Points[MAX_POINTS];
  19. int _NumSegments;
  20. float _Thickness;
  21. float3 get_cubic_roots(float4 coefficients) {
  22. // eliminate a3 by dividing it out
  23. coefficients /= coefficients.x;
  24. float a2 = coefficients.y;
  25. float a1 = coefficients.z;
  26. float a0 = coefficients.w;
  27. // follow along at http://mathworld.wolfram.com/CubicFormula.html
  28. // thanks also to https://www.shadertoy.com/view/XdB3Ww
  29. float Q = (3. * a1 - (a2 * a2)) / 9.;
  30. float R = (9. * a2 * a1 - 27. * a0 - 2. * (a2 * a2 * a2)) / 54.;
  31. float D = Q * Q * Q + R * R;
  32. if (D < 0.) {
  33. float theta = acos(R / sqrt(-(Q * Q * Q)));
  34. float3 i1 = float3(0., 2. * PI, 4. * PI) + theta;
  35. float3 i2 = cos(i1 / 3.);
  36. return 2. * sqrt(-Q) * i2 - 1/3. * a2;
  37. } else {
  38. // if sqrt(D) and R are very close to each other, R - sd wigs
  39. // out due to numerical cancellation. so multiply by the
  40. // conjugate to get a slightly different equation that avoids
  41. // that operation (i.e., we end up doing R + sd instead).
  42. float sd = sqrt(D);
  43. // float2 st = float2(sd, -sd) + R;
  44. // if (sign(R) == sign(sd))
  45. // st = float2(R + sd, -pow(Q, 3) / (R + sd));
  46. // else
  47. // st = float2(R - sd, -pow(Q, 3) / (R - sd));
  48. float rsd = (when_neq(sign(R), sign(sd)) * -2 + 1) * sd + R;
  49. float2 st = float2(rsd, -(Q * Q * Q) / rsd);
  50. // preserve the sign of R +- sqrt(D) after taking the cube root
  51. st = sign(st) * pow(abs(st), 1/3.);
  52. float r = -1/3. * a2 + st.x + st.y;
  53. return float3(r, 0, 0);
  54. }
  55. }
  56. float2 distance_to_segment(float2 M, float2 b0, float2 b1, float2 b2) {
  57. // get the coefficients, thanks to
  58. // http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html
  59. // note that when b1 is too close to the midpoint between b0 and b2, B ~= 0 and we get problems.
  60. // (handling that case in user space before we even get here)
  61. float2 A = b1 - b0;
  62. float2 B = b2 - b1 - A;
  63. float2 Mp = b0 - M;
  64. float a = dot(B, B);
  65. float b = 3. * dot(A, B);
  66. float c = 2. * dot(A, A) + dot(Mp, B);
  67. float d = dot(Mp, A);
  68. float3 roots = clamp(get_cubic_roots(float4(a, b, c, d)), 0, 1);
  69. float2 D = 2. * A;
  70. // thanks yet again to http://alienryderflex.com/polyspline/
  71. float flip = 1;
  72. #if FILLED_PATH_1 || FILLED_PATH_2 || FILLED_PATH_4 || FILLED_PATH_8 || FILLED_PATH_16 || FILLED_PATH_24 || FILLED_PATH_32
  73. // make sure M.y doesn't equal b0.y or b2.y, which could cause F1 or F2 to be exactly 0
  74. // (becomes a problem especially when rotating)
  75. float testY = M.y + when_lt(abs(b0.y - M.y), .0001) * .0002;
  76. testY += when_lt(abs(b2.y - testY), .0001) * .0002;
  77. float bottomPart=2.*(b0.y+b2.y-b1.y-b1.y);
  78. // prevent division-by-zero (also handled in user space)
  79. // if (abs(bottomPart) <= 0.0001) {
  80. // b1.y += 0.0001;
  81. // bottomPart = -0.0004;
  82. // }
  83. float sRoot=D.y;
  84. sRoot*=sRoot;
  85. sRoot-=2.*bottomPart*(b0.y - testY);
  86. if (sRoot >= 0) {
  87. sRoot=sqrt(sRoot);
  88. float topPart=2.*(b0.y-b1.y);
  89. float F1 = (topPart+sRoot)/bottomPart;
  90. float F2 = (topPart-sRoot)/bottomPart;
  91. if (F1>=0. && F1<=1.) {
  92. float xPart=b0.x+F1*(b1.x-b0.x);
  93. if (xPart+F1*(b1.x+F1*(b2.x-b1.x)-xPart)<M.x)
  94. flip *= -1;
  95. }
  96. if (F2>=0. && F2<=1.) {
  97. float xPart=b0.x+F2*(b1.x-b0.x);
  98. if (xPart+F2*(b1.x+F2*(b2.x-b1.x)-xPart)<M.x)
  99. flip *= -1;
  100. }
  101. }
  102. #endif
  103. // find the positions on the curve of each root
  104. // thanks to https://www.shadertoy.com/view/XdB3Ww for this simplification
  105. float2 p1 = roots.x * (D + B * roots.x) + b0;
  106. float2 p2 = roots.y * (D + B * roots.y) + b0;
  107. float2 p3 = roots.z * (D + B * roots.z) + b0;
  108. // figure out which point is closest to M
  109. float dist1 = length(p1 - M);
  110. float dist2 = length(p2 - M);
  111. float dist3 = length(p3 - M);
  112. float dist = min(min(dist1, dist2), dist3);
  113. return float2(dist, flip);
  114. }
  115. float2 distance_to_path(float2 pos) {
  116. float closest_distance = 9999999;
  117. int odd_nodes = -1; // used for testing if the point is inside a filled path
  118. // you can't do variable-length loops in webgl (or es2 technically I think),
  119. // hence the constant...so we have to iterate through MAX_SEGMENTS rather than
  120. // _NumSegments which is the number of vertices actually in our poly
  121. for (int i = 0; i < MAX_SEGMENTS; i++) {
  122. // loop_over is 1 when we're past the number of sides in the poly
  123. float loop_over = when_ge(i, _NumSegments);
  124. float2 b0 = _Points[i * 3 + 0];
  125. float2 b1 = _Points[i * 3 + 1];
  126. float2 b2 = _Points[i * 3 + 2];
  127. float2 result = distance_to_segment(pos, b0, b1, b2) + loop_over * 9999999;
  128. float dist = result.x;
  129. closest_distance = min(dist, closest_distance);
  130. if (_Points[i * 3].z == 1)
  131. odd_nodes *= result.y / (loop_over * (result.y - 1) + 1);
  132. }
  133. return odd_nodes * closest_distance + _Thickness;
  134. }
  135. fixed4 frag(v2f i) : SV_Target {
  136. float2 pos = prepare(i.uv, i.modelPos.z);
  137. float dist = distance_to_path(pos);
  138. float is_outside = when_lt(dist, 0);
  139. fixed4 color = color_from_distance(dist, fill(i.uv), _OutlineColor) * i.color;
  140. if (_PreMultiplyAlpha == 1)
  141. color.rgb *= color.a;
  142. if (_UseClipRect == 1)
  143. color.a *= UnityGet2DClipping(i.modelPos.xy, _ClipRect);
  144. clip(color.a - 0.001);
  145. return (1 - is_outside) * color + is_outside * fixed4(0, 0, 0, 0);
  146. }