Files

279 lines
13 KiB
C#

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();
}
}
}