| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- namespace Shapes2D {
- using UnityEngine;
- class PolyMap {
- struct DistanceResult {
- public int index, index2;
- }
- // http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
- static float DistanceToLineSegment(Vector2 pos, Vector2 p1, Vector2 p2) {
- // vector operations are pretty slow! do it all manually...
- float dx = p2.x - p1.x;
- float dy = p2.y - p1.y;
- float dist = Mathf.Sqrt(dx * dx + dy * dy);
- float l2 = dist * dist;
- float c1x = pos.x - p1.x;
- float c1y = pos.y - p1.y;
- float c2x = p2.x - p1.x;
- float c2y = p2.y - p1.y;
- float dot = c1x * c2x + c1y * c2y;
- dot /= l2;
- float t = dot < 0 ? 0 : dot;
- t = t > 1 ? 1 : t;
- float projx = p1.x + t * dx;
- float projy = p1.y + t * dy;
- dx = projx - pos.x;
- dy = projy - pos.y;
- return Mathf.Sqrt(dx * dx + dy * dy);
- }
- static DistanceResult GetClosestLineSegments(Vector2 pos, Vector2[] vertices) {
- DistanceResult dr;
- dr.index = -1;
- dr.index2 = -1;
- float closestDistance = -1;
- float closestDistance2 = -1;
- int j = vertices.Length - 1;
- for (int i = 0; i < vertices.Length; i++) {
- float dist = DistanceToLineSegment(pos, vertices[i], vertices[j]);
- if (dr.index == -1 || dist < closestDistance) {
- dr.index2 = dr.index;
- closestDistance2 = closestDistance;
- dr.index = i;
- closestDistance = dist;
- } else if (dr.index2 == -1 || dist < closestDistance2) {
- dr.index2 = i;
- closestDistance2 = dist;
- }
- j = i;
- }
- return dr;
- }
- // thanks to http://alienryderflex.com/polygon/. if the number of nodes (points
- // where a polygon line crosses the horizontal axis of the test position) to the
- // left of the test position is odd, then the point is inside the poly!
- static bool PointIsInsidePoly(Vector2 pos, Vector2[] vertices) {
- bool oddNodes = false;
- int j = vertices.Length - 1;
- for (int i = 0; i < vertices.Length; i++) {
- Vector2 vi = vertices[i];
- Vector2 vj = vertices[j];
- if ((vi.y < pos.y && vj.y >= pos.y || vj.y < pos.y && vi.y >= pos.y)
- && (vi.x <= pos.x || vj.x <= pos.x)) {
- if (vi.x + (pos.y - vi.y) / (vj.y - vi.y) * (vj.x - vi.x) < pos.x)
- oddNodes = !oddNodes;
- }
- j = i;
- }
- return oddNodes;
- }
- static bool IntersectsVerticalLineSegment(Vector2 v1, Vector2 v2, Vector2 p1, Vector2 p2) {
- float pminx = p1.x < p2.x ? p1.x : p2.x;
- float pminy = p1.y < p2.y ? p1.y : p2.y;
- // float vminx = v1.x < v2.x ? v1.x : v2.x;
- float vminy = v1.y < v2.y ? v1.y : v2.y;
- float pmaxx = p1.x > p2.x ? p1.x : p2.x;
- float pmaxy = p1.y > p2.y ? p1.y : p2.y;
- // float vmaxx = v1.x > v2.x ? v1.x : v2.x;
- float vmaxy = v1.y > v2.y ? v1.y : v2.y;
- if (pminx > v1.x || pmaxx < v1.x || pmaxy < vminy || pminy > vmaxy)
- return false;
- if (p2.x == p1.x)
- return p1.x == v1.x && ((pminy >= vminy && pminy <= vmaxy) || (pmaxy >= vminy && pmaxy <= vmaxy));
- float m = (p2.y - p1.y) / (p2.x - p1.x);
- float b = p2.y - m * p2.x;
- float y = m * v1.x + b;
- return y >= vminy && y <= vmaxy;
- }
- static bool IntersectsHorizontalLineSegment(Vector2 v1, Vector2 v2, Vector2 p1, Vector2 p2) {
- float pminx = p1.x < p2.x ? p1.x : p2.x;
- float pminy = p1.y < p2.y ? p1.y : p2.y;
- float vminx = v1.x < v2.x ? v1.x : v2.x;
- // float vminy = v1.y < v2.y ? v1.y : v2.y;
- float pmaxx = p1.x > p2.x ? p1.x : p2.x;
- float pmaxy = p1.y > p2.y ? p1.y : p2.y;
- float vmaxx = v1.x > v2.x ? v1.x : v2.x;
- // float vmaxy = v1.y > v2.y ? v1.y : v2.y;
- if (pminx > vmaxx || pmaxx < vminx || pmaxy < v1.y || pminy > v1.y)
- return false;
- if (p2.x == p1.x)
- return p1.x >= vminx && p2.x <= vmaxx;
- float m = (p2.y - p1.y) / (p2.x - p1.x);
- float b = p2.y - m * p2.x;
- float x = (v1.y - b) / m;
- return x >= vminx && x <= vmaxx;
- }
- static int RectIntersectsPolyEdge(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br,
- Vector2[] vertices, bool[] results) {
- int edgeCount = 0;
- int j = vertices.Length - 1;
- for (int i = 0; i < vertices.Length; i++) {
- Vector2 p1 = vertices[i];
- Vector2 p2 = vertices[j];
- bool top = IntersectsHorizontalLineSegment(tl, tr, p1, p2);
- bool bottom = IntersectsHorizontalLineSegment(bl, br, p1, p2);
- bool left = IntersectsVerticalLineSegment(tl, bl, p1, p2);
- bool right = IntersectsVerticalLineSegment(tr, br, p1, p2);
- results[i] = top || bottom || left || right;
- if (top || bottom || left || right)
- edgeCount ++;
- j = i;
- }
- return edgeCount;
- }
- static int RectContainsVertex(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br,
- Vector2[] vertices, bool[] results) {
- int vertCount = 0;
- int i = 0;
- foreach (Vector2 v in vertices) {
- results[i] = v.x >= tl.x && v.x <= tr.x && v.y >= bl.y && v.y <= tl.y;
- if (results[i])
- vertCount ++;
- i ++;
- }
- return vertCount;
- }
- // which side of the line is the point on? consistent winding means this tells us
- // if a point is on the inside of a convex polygon or not (or a convex section of a
- // concave poly which is what we care about), assuming it isn't excluded by any of
- // the other lines.
- static bool PointLineTest(Vector2 pos, Vector2 p1, Vector2 p2) {
- return (pos.x - p1.x) * (p2.y - p1.y) - (pos.y - p1.y) * (p2.x - p1.x) > 0;
- }
- // maybe could use Vector2.Angle() instead?
- static float GetAngleBetween(Vector2 v1, Vector2 v2) {
- float a1 = Mathf.Atan2(v1.y, v1.x) * Mathf.Rad2Deg;
- float a2 = Mathf.Atan2(v2.y, v2.x) * Mathf.Rad2Deg;
- if (a1 < 0)
- a1 += 360;
- if (a2 < 0)
- a2 += 360;
- float result = a1 - a2;
- if (result < 0)
- result += 360;
- return result;
- }
- public static void GeneratePolyMap(Vector2[] vertices, Texture2D polyMap) {
- if (polyMap.width != polyMap.height)
- throw new System.InvalidOperationException("Poly map texture must be square!");
- int resolution = polyMap.width;
- Vector2 tex = new Vector2(1f / resolution, 1f / resolution);
- bool[] edgeIntersections = new bool[vertices.Length];
- bool[] vertIntersections = new bool[vertices.Length];
- // for each texel
- for (int x = 0; x < resolution; x++) {
- for (int y = 0; y < resolution; y++) {
- Vector2 center = new Vector2(x, y) * tex.x + tex / 2 - new Vector2(0.5f, 0.5f);
- Vector2 tl = center + new Vector2(-tex.x / 2, tex.y / 2);
- Vector2 tr = center + tex / 2;
- Vector2 bl = center - tex / 2;
- Vector2 br = center + new Vector2(tex.x / 2, -tex.y / 2);
- int edgeCount = RectIntersectsPolyEdge(tl, tr, bl, br, vertices, edgeIntersections);
- bool inside = PointIsInsidePoly(center, vertices);
- // get the closest line segments to the texel center. it's much faster
- // for the shader to just look at a few line segments than all of them, but
- // when the closest lines to the center of the texel aren't the closest for all
- // pixels in the texel there will be rendering flaws (though hopefully one of them
- // will be the closest one to each pixel in reasonably spaced polygons).
- // note that in most cases we could do a simpler algorithm that just checks the
- // closest of the two nearest lines in the fragment shader and colors pixels
- // inside of the winner, but it gets fuzzy in many cases where fragments are
- // exactly as far (or almost exactly as far) from each line.
- DistanceResult dr = GetClosestLineSegments(center, vertices);
- // we'll send a 'mode' to the shader indicating what it should do.
- // 0 = all pixels in the texel are outside the poly
- // 1 = all pixels in the texel are inside the poly
- // 2 = must be inside the closest line
- // 3 = must be inside both lines
- // 4 = must be inside either line
- int mode = 0;
- if (edgeCount == 0) {
- // no intersection, so all points are on the same side of the poly
- mode = inside ? 1 : 0;
- } else if (edgeCount == 1) {
- // intersects with 1 edge, just be inside that edge.
- // make sure dr.index is the intersecting edge - it's possible it isn't.
- mode = 2;
- if (!edgeIntersections[dr.index]) {
- int temp = dr.index;
- dr.index = dr.index2;
- dr.index2 = temp;
- }
- // mode = 5;
- } else if (RectContainsVertex(tl, tr, bl, br, vertices, vertIntersections) > 0) {
- // the texel contains >= 1 vertex, so we need to get the angle between
- // the vert's lines to figure out what to do. multiple verts in a single texel
- // are not supported.
- // find the relevant vertex
- Vector2 v = Vector2.zero;
- int i;
- for (i = 0; i < vertices.Length; i++) {
- if (vertIntersections[i]) {
- v = vertices[i];
- break;
- }
- }
- // find the neighboring verts. make sure their lines are the closest segments too.
- // v1 -- v -- v2
- dr.index = i;
- int j = i == 0 ? vertices.Length - 1 : i - 1;
- Vector2 v1 = vertices[j];
- j = i == vertices.Length - 1 ? 0 : i + 1;
- dr.index2 = j;
- Vector2 v2 = vertices[j];
- // find the angle between the two lines
- float angle = GetAngleBetween(v1 - v, v2 - v);
- // each pixel needs to be inside the joint
- if (angle <= 180) {
- mode = 3;
- } else {
- mode = 4;
- }
- // mode = 5;
- } else {
- // edge count is > 1 and no vertex intersection.
- // we can tell if the lines are facing in the same direction by using the
- // center as a reference and checking if it is on the same or different
- // side of each line, and whether or not it's inside the poly.
- int found = 0;
- bool test1 = false, test2 = false;
- for (int i = 0; i < vertices.Length; i++) {
- if (!edgeIntersections[i])
- continue;
- int j = i == 0 ? vertices.Length - 1 : i - 1;
- if (found == 0)
- test1 = PointLineTest(center, vertices[i], vertices[j]);
- else
- test2 = PointLineTest(center, vertices[i], vertices[j]);
- found ++;
- if (found == 2)
- break;
- }
- if ((test1 == test2) && inside)
- mode = 3;
- else if ((test1 == test2) && !inside)
- mode = 4;
- else if ((test1 != test2) && inside)
- mode = 4;
- else if ((test1 != test2) && !inside)
- mode = 3;
- // mode = 5;
- }
- polyMap.SetPixel(x, y, new Color(dr.index / 256f, dr.index2 / 256f, 0, mode / 256f));
- }
- }
- polyMap.Apply();
- }
- }
- }
|