forked from Shardstone/trail-into-darkness
First commit on my server, yey!
This commit is contained in:
200
Packages/com.jovian.zonesystem/Runtime/PolygonUtils.cs
Normal file
200
Packages/com.jovian.zonesystem/Runtime/PolygonUtils.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.ZoneSystem {
|
||||
public static class PolygonUtils {
|
||||
/// <summary>
|
||||
/// Ray-casting point-in-polygon test (Jordan curve theorem).
|
||||
/// Works on any plane — caller projects the world position first via MapPlaneUtility.
|
||||
/// Handles edge and vertex cases robustly.
|
||||
/// </summary>
|
||||
/// <param name="point">2D point already projected onto the polygon's plane.</param>
|
||||
/// <param name="polygon">Polygon vertices in the same 2D space.</param>
|
||||
public static bool PointInPolygon(Vector2 point, List<Vector2> polygon) {
|
||||
if(polygon == null || polygon.Count < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float px = point.x;
|
||||
float py = point.y;
|
||||
bool inside = false;
|
||||
int count = polygon.Count;
|
||||
int j = count - 1;
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
float xi = polygon[i].x, yi = polygon[i].y;
|
||||
float xj = polygon[j].x, yj = polygon[j].y;
|
||||
|
||||
// Crossing test: does the edge (j→i) cross the horizontal ray from point?
|
||||
bool crosses = (yi > py) != (yj > py) &&
|
||||
px < ((xj - xi) * (py - yi) / (yj - yi)) + xi;
|
||||
|
||||
if(crosses) {
|
||||
inside = !inside;
|
||||
}
|
||||
|
||||
j = i;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overload that accepts a world position and projects it onto the given plane
|
||||
/// before testing — this is the primary API used by ZoneManager.
|
||||
/// </summary>
|
||||
public static bool PointInPolygon(Vector3 worldPos, List<Vector2> polygon, MapPlane plane) {
|
||||
Vector2 projected = MapPlaneUtility.ProjectToPlane(worldPos, plane);
|
||||
return PointInPolygon(projected, polygon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the centroid of a polygon (for label placement in the editor).
|
||||
/// </summary>
|
||||
public static Vector2 Centroid(List<Vector2> polygon) {
|
||||
if(polygon == null || polygon.Count == 0) {
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
Vector2 sum = Vector2.zero;
|
||||
foreach(Vector2 pt in polygon) {
|
||||
sum += pt;
|
||||
}
|
||||
return sum / polygon.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the approximate axis-aligned bounding box of a polygon.
|
||||
/// Useful for a cheap pre-check before running the full ray-cast test.
|
||||
/// </summary>
|
||||
public static (Vector2 min, Vector2 max) Bounds(List<Vector2> polygon) {
|
||||
if(polygon == null || polygon.Count == 0) {
|
||||
return (Vector2.zero, Vector2.zero);
|
||||
}
|
||||
|
||||
Vector2 min = polygon[0], max = polygon[0];
|
||||
foreach(Vector2 pt in polygon) {
|
||||
if(pt.x < min.x) {
|
||||
min.x = pt.x;
|
||||
}
|
||||
if(pt.y < min.y) {
|
||||
min.y = pt.y;
|
||||
}
|
||||
if(pt.x > max.x) {
|
||||
max.x = pt.x;
|
||||
}
|
||||
if(pt.y > max.y) {
|
||||
max.y = pt.y;
|
||||
}
|
||||
}
|
||||
return (min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast AABB pre-check. Call this before PointInPolygon to skip the
|
||||
/// ray-cast for points clearly outside the bounding box.
|
||||
/// </summary>
|
||||
public static bool PointInBounds(Vector2 point, Vector2 min, Vector2 max) {
|
||||
return point.x >= min.x && point.x <= max.x &&
|
||||
point.y >= min.y && point.y <= max.y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ear-clipping triangulation for simple (non-self-intersecting) polygons.
|
||||
/// Returns a list of triangle index triplets into the original vertex list.
|
||||
/// Supports both convex and concave polygons.
|
||||
/// </summary>
|
||||
public static List<int> Triangulate(List<Vector2> polygon) {
|
||||
List<int> triangles = new List<int>();
|
||||
int n = polygon.Count;
|
||||
if(n < 3) {
|
||||
return triangles;
|
||||
}
|
||||
|
||||
// Build index list
|
||||
List<int> indices = new List<int>(n);
|
||||
bool clockwise = SignedArea(polygon) < 0f;
|
||||
for(int i = 0; i < n; i++) {
|
||||
indices.Add(clockwise ? i : n - 1 - i);
|
||||
}
|
||||
|
||||
int remaining = n;
|
||||
int failSafe = remaining * 2;
|
||||
|
||||
int v = remaining - 1;
|
||||
while(remaining > 2) {
|
||||
if(failSafe-- <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
int u = v;
|
||||
if(u >= remaining) {
|
||||
u = 0;
|
||||
}
|
||||
v = u + 1;
|
||||
if(v >= remaining) {
|
||||
v = 0;
|
||||
}
|
||||
int w = v + 1;
|
||||
if(w >= remaining) {
|
||||
w = 0;
|
||||
}
|
||||
|
||||
if(IsEar(polygon, indices, u, v, w, remaining)) {
|
||||
triangles.Add(indices[u]);
|
||||
triangles.Add(indices[v]);
|
||||
triangles.Add(indices[w]);
|
||||
indices.RemoveAt(v);
|
||||
remaining--;
|
||||
failSafe = remaining * 2;
|
||||
}
|
||||
}
|
||||
|
||||
return triangles;
|
||||
}
|
||||
|
||||
private static float SignedArea(List<Vector2> polygon) {
|
||||
float area = 0f;
|
||||
int count = polygon.Count;
|
||||
for(int i = 0; i < count; i++) {
|
||||
Vector2 a = polygon[i];
|
||||
Vector2 b = polygon[(i + 1) % count];
|
||||
area += (b.x - a.x) * (b.y + a.y);
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
private static bool IsEar(List<Vector2> polygon, List<int> indices, int u, int v, int w, int remaining) {
|
||||
Vector2 a = polygon[indices[u]];
|
||||
Vector2 b = polygon[indices[v]];
|
||||
Vector2 c = polygon[indices[w]];
|
||||
|
||||
// Must be convex (counter-clockwise winding after we've ensured CCW order)
|
||||
float cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
||||
if(cross <= 0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No other vertex must be inside this triangle
|
||||
for(int p = 0; p < remaining; p++) {
|
||||
if(p == u || p == v || p == w) {
|
||||
continue;
|
||||
}
|
||||
if(PointInTriangle(polygon[indices[p]], a, b, c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool PointInTriangle(Vector2 p, Vector2 a, Vector2 b, Vector2 c) {
|
||||
float d1 = (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y);
|
||||
float d2 = (p.x - c.x) * (b.y - c.y) - (b.x - c.x) * (p.y - c.y);
|
||||
float d3 = (p.x - a.x) * (c.y - a.y) - (c.x - a.x) * (p.y - a.y);
|
||||
bool hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
|
||||
bool hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
|
||||
return !(hasNeg && hasPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user