PolyMap.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. namespace Shapes2D {
  2. using UnityEngine;
  3. class PolyMap {
  4. struct DistanceResult {
  5. public int index, index2;
  6. }
  7. // http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
  8. static float DistanceToLineSegment(Vector2 pos, Vector2 p1, Vector2 p2) {
  9. // vector operations are pretty slow! do it all manually...
  10. float dx = p2.x - p1.x;
  11. float dy = p2.y - p1.y;
  12. float dist = Mathf.Sqrt(dx * dx + dy * dy);
  13. float l2 = dist * dist;
  14. float c1x = pos.x - p1.x;
  15. float c1y = pos.y - p1.y;
  16. float c2x = p2.x - p1.x;
  17. float c2y = p2.y - p1.y;
  18. float dot = c1x * c2x + c1y * c2y;
  19. dot /= l2;
  20. float t = dot < 0 ? 0 : dot;
  21. t = t > 1 ? 1 : t;
  22. float projx = p1.x + t * dx;
  23. float projy = p1.y + t * dy;
  24. dx = projx - pos.x;
  25. dy = projy - pos.y;
  26. return Mathf.Sqrt(dx * dx + dy * dy);
  27. }
  28. static DistanceResult GetClosestLineSegments(Vector2 pos, Vector2[] vertices) {
  29. DistanceResult dr;
  30. dr.index = -1;
  31. dr.index2 = -1;
  32. float closestDistance = -1;
  33. float closestDistance2 = -1;
  34. int j = vertices.Length - 1;
  35. for (int i = 0; i < vertices.Length; i++) {
  36. float dist = DistanceToLineSegment(pos, vertices[i], vertices[j]);
  37. if (dr.index == -1 || dist < closestDistance) {
  38. dr.index2 = dr.index;
  39. closestDistance2 = closestDistance;
  40. dr.index = i;
  41. closestDistance = dist;
  42. } else if (dr.index2 == -1 || dist < closestDistance2) {
  43. dr.index2 = i;
  44. closestDistance2 = dist;
  45. }
  46. j = i;
  47. }
  48. return dr;
  49. }
  50. // thanks to http://alienryderflex.com/polygon/. if the number of nodes (points
  51. // where a polygon line crosses the horizontal axis of the test position) to the
  52. // left of the test position is odd, then the point is inside the poly!
  53. static bool PointIsInsidePoly(Vector2 pos, Vector2[] vertices) {
  54. bool oddNodes = false;
  55. int j = vertices.Length - 1;
  56. for (int i = 0; i < vertices.Length; i++) {
  57. Vector2 vi = vertices[i];
  58. Vector2 vj = vertices[j];
  59. if ((vi.y < pos.y && vj.y >= pos.y || vj.y < pos.y && vi.y >= pos.y)
  60. && (vi.x <= pos.x || vj.x <= pos.x)) {
  61. if (vi.x + (pos.y - vi.y) / (vj.y - vi.y) * (vj.x - vi.x) < pos.x)
  62. oddNodes = !oddNodes;
  63. }
  64. j = i;
  65. }
  66. return oddNodes;
  67. }
  68. static bool IntersectsVerticalLineSegment(Vector2 v1, Vector2 v2, Vector2 p1, Vector2 p2) {
  69. float pminx = p1.x < p2.x ? p1.x : p2.x;
  70. float pminy = p1.y < p2.y ? p1.y : p2.y;
  71. // float vminx = v1.x < v2.x ? v1.x : v2.x;
  72. float vminy = v1.y < v2.y ? v1.y : v2.y;
  73. float pmaxx = p1.x > p2.x ? p1.x : p2.x;
  74. float pmaxy = p1.y > p2.y ? p1.y : p2.y;
  75. // float vmaxx = v1.x > v2.x ? v1.x : v2.x;
  76. float vmaxy = v1.y > v2.y ? v1.y : v2.y;
  77. if (pminx > v1.x || pmaxx < v1.x || pmaxy < vminy || pminy > vmaxy)
  78. return false;
  79. if (p2.x == p1.x)
  80. return p1.x == v1.x && ((pminy >= vminy && pminy <= vmaxy) || (pmaxy >= vminy && pmaxy <= vmaxy));
  81. float m = (p2.y - p1.y) / (p2.x - p1.x);
  82. float b = p2.y - m * p2.x;
  83. float y = m * v1.x + b;
  84. return y >= vminy && y <= vmaxy;
  85. }
  86. static bool IntersectsHorizontalLineSegment(Vector2 v1, Vector2 v2, Vector2 p1, Vector2 p2) {
  87. float pminx = p1.x < p2.x ? p1.x : p2.x;
  88. float pminy = p1.y < p2.y ? p1.y : p2.y;
  89. float vminx = v1.x < v2.x ? v1.x : v2.x;
  90. // float vminy = v1.y < v2.y ? v1.y : v2.y;
  91. float pmaxx = p1.x > p2.x ? p1.x : p2.x;
  92. float pmaxy = p1.y > p2.y ? p1.y : p2.y;
  93. float vmaxx = v1.x > v2.x ? v1.x : v2.x;
  94. // float vmaxy = v1.y > v2.y ? v1.y : v2.y;
  95. if (pminx > vmaxx || pmaxx < vminx || pmaxy < v1.y || pminy > v1.y)
  96. return false;
  97. if (p2.x == p1.x)
  98. return p1.x >= vminx && p2.x <= vmaxx;
  99. float m = (p2.y - p1.y) / (p2.x - p1.x);
  100. float b = p2.y - m * p2.x;
  101. float x = (v1.y - b) / m;
  102. return x >= vminx && x <= vmaxx;
  103. }
  104. static int RectIntersectsPolyEdge(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br,
  105. Vector2[] vertices, bool[] results) {
  106. int edgeCount = 0;
  107. int j = vertices.Length - 1;
  108. for (int i = 0; i < vertices.Length; i++) {
  109. Vector2 p1 = vertices[i];
  110. Vector2 p2 = vertices[j];
  111. bool top = IntersectsHorizontalLineSegment(tl, tr, p1, p2);
  112. bool bottom = IntersectsHorizontalLineSegment(bl, br, p1, p2);
  113. bool left = IntersectsVerticalLineSegment(tl, bl, p1, p2);
  114. bool right = IntersectsVerticalLineSegment(tr, br, p1, p2);
  115. results[i] = top || bottom || left || right;
  116. if (top || bottom || left || right)
  117. edgeCount ++;
  118. j = i;
  119. }
  120. return edgeCount;
  121. }
  122. static int RectContainsVertex(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br,
  123. Vector2[] vertices, bool[] results) {
  124. int vertCount = 0;
  125. int i = 0;
  126. foreach (Vector2 v in vertices) {
  127. results[i] = v.x >= tl.x && v.x <= tr.x && v.y >= bl.y && v.y <= tl.y;
  128. if (results[i])
  129. vertCount ++;
  130. i ++;
  131. }
  132. return vertCount;
  133. }
  134. // which side of the line is the point on? consistent winding means this tells us
  135. // if a point is on the inside of a convex polygon or not (or a convex section of a
  136. // concave poly which is what we care about), assuming it isn't excluded by any of
  137. // the other lines.
  138. static bool PointLineTest(Vector2 pos, Vector2 p1, Vector2 p2) {
  139. return (pos.x - p1.x) * (p2.y - p1.y) - (pos.y - p1.y) * (p2.x - p1.x) > 0;
  140. }
  141. // maybe could use Vector2.Angle() instead?
  142. static float GetAngleBetween(Vector2 v1, Vector2 v2) {
  143. float a1 = Mathf.Atan2(v1.y, v1.x) * Mathf.Rad2Deg;
  144. float a2 = Mathf.Atan2(v2.y, v2.x) * Mathf.Rad2Deg;
  145. if (a1 < 0)
  146. a1 += 360;
  147. if (a2 < 0)
  148. a2 += 360;
  149. float result = a1 - a2;
  150. if (result < 0)
  151. result += 360;
  152. return result;
  153. }
  154. public static void GeneratePolyMap(Vector2[] vertices, Texture2D polyMap) {
  155. if (polyMap.width != polyMap.height)
  156. throw new System.InvalidOperationException("Poly map texture must be square!");
  157. int resolution = polyMap.width;
  158. Vector2 tex = new Vector2(1f / resolution, 1f / resolution);
  159. bool[] edgeIntersections = new bool[vertices.Length];
  160. bool[] vertIntersections = new bool[vertices.Length];
  161. // for each texel
  162. for (int x = 0; x < resolution; x++) {
  163. for (int y = 0; y < resolution; y++) {
  164. Vector2 center = new Vector2(x, y) * tex.x + tex / 2 - new Vector2(0.5f, 0.5f);
  165. Vector2 tl = center + new Vector2(-tex.x / 2, tex.y / 2);
  166. Vector2 tr = center + tex / 2;
  167. Vector2 bl = center - tex / 2;
  168. Vector2 br = center + new Vector2(tex.x / 2, -tex.y / 2);
  169. int edgeCount = RectIntersectsPolyEdge(tl, tr, bl, br, vertices, edgeIntersections);
  170. bool inside = PointIsInsidePoly(center, vertices);
  171. // get the closest line segments to the texel center. it's much faster
  172. // for the shader to just look at a few line segments than all of them, but
  173. // when the closest lines to the center of the texel aren't the closest for all
  174. // pixels in the texel there will be rendering flaws (though hopefully one of them
  175. // will be the closest one to each pixel in reasonably spaced polygons).
  176. // note that in most cases we could do a simpler algorithm that just checks the
  177. // closest of the two nearest lines in the fragment shader and colors pixels
  178. // inside of the winner, but it gets fuzzy in many cases where fragments are
  179. // exactly as far (or almost exactly as far) from each line.
  180. DistanceResult dr = GetClosestLineSegments(center, vertices);
  181. // we'll send a 'mode' to the shader indicating what it should do.
  182. // 0 = all pixels in the texel are outside the poly
  183. // 1 = all pixels in the texel are inside the poly
  184. // 2 = must be inside the closest line
  185. // 3 = must be inside both lines
  186. // 4 = must be inside either line
  187. int mode = 0;
  188. if (edgeCount == 0) {
  189. // no intersection, so all points are on the same side of the poly
  190. mode = inside ? 1 : 0;
  191. } else if (edgeCount == 1) {
  192. // intersects with 1 edge, just be inside that edge.
  193. // make sure dr.index is the intersecting edge - it's possible it isn't.
  194. mode = 2;
  195. if (!edgeIntersections[dr.index]) {
  196. int temp = dr.index;
  197. dr.index = dr.index2;
  198. dr.index2 = temp;
  199. }
  200. // mode = 5;
  201. } else if (RectContainsVertex(tl, tr, bl, br, vertices, vertIntersections) > 0) {
  202. // the texel contains >= 1 vertex, so we need to get the angle between
  203. // the vert's lines to figure out what to do. multiple verts in a single texel
  204. // are not supported.
  205. // find the relevant vertex
  206. Vector2 v = Vector2.zero;
  207. int i;
  208. for (i = 0; i < vertices.Length; i++) {
  209. if (vertIntersections[i]) {
  210. v = vertices[i];
  211. break;
  212. }
  213. }
  214. // find the neighboring verts. make sure their lines are the closest segments too.
  215. // v1 -- v -- v2
  216. dr.index = i;
  217. int j = i == 0 ? vertices.Length - 1 : i - 1;
  218. Vector2 v1 = vertices[j];
  219. j = i == vertices.Length - 1 ? 0 : i + 1;
  220. dr.index2 = j;
  221. Vector2 v2 = vertices[j];
  222. // find the angle between the two lines
  223. float angle = GetAngleBetween(v1 - v, v2 - v);
  224. // each pixel needs to be inside the joint
  225. if (angle <= 180) {
  226. mode = 3;
  227. } else {
  228. mode = 4;
  229. }
  230. // mode = 5;
  231. } else {
  232. // edge count is > 1 and no vertex intersection.
  233. // we can tell if the lines are facing in the same direction by using the
  234. // center as a reference and checking if it is on the same or different
  235. // side of each line, and whether or not it's inside the poly.
  236. int found = 0;
  237. bool test1 = false, test2 = false;
  238. for (int i = 0; i < vertices.Length; i++) {
  239. if (!edgeIntersections[i])
  240. continue;
  241. int j = i == 0 ? vertices.Length - 1 : i - 1;
  242. if (found == 0)
  243. test1 = PointLineTest(center, vertices[i], vertices[j]);
  244. else
  245. test2 = PointLineTest(center, vertices[i], vertices[j]);
  246. found ++;
  247. if (found == 2)
  248. break;
  249. }
  250. if ((test1 == test2) && inside)
  251. mode = 3;
  252. else if ((test1 == test2) && !inside)
  253. mode = 4;
  254. else if ((test1 != test2) && inside)
  255. mode = 4;
  256. else if ((test1 != test2) && !inside)
  257. mode = 3;
  258. // mode = 5;
  259. }
  260. polyMap.SetPixel(x, y, new Color(dr.index / 256f, dr.index2 / 256f, 0, mode / 256f));
  261. }
  262. }
  263. polyMap.Apply();
  264. }
  265. }
  266. }