using System.Collections.Generic;
using UnityEngine;
namespace Puzzy.MathUtils
{
public static class VectorExtensions
{
public static List<int> Triangulate(this IList<Vector3> vertices)
{
var indices = new List<int>();
int n = vertices.Count;
if (n < 3)
{
Debug.LogError("Cannot triangulate fewer than 3 vertices");
return indices;
}
bool isClosed = Vector3.Distance(vertices[0], vertices[n - 1]) < 0.0001f;
if (isClosed)
{
n--;
}
List<int> vertexIndices = new List<int>();
for (int i = 0; i < n; i++)
vertexIndices.Add(i);
float signedArea = 0;
for (int i = 0; i < n; i++)
{
Vector2 current = vertices[i].ToXY();
Vector2 next = vertices[(i + 1) % n].ToXY();
signedArea += (current.x * next.y - next.x * current.y);
}
bool isCcw = signedArea > 0;
if (!isCcw)
{
vertexIndices.Reverse();
}
int attempts = 0;
while (vertexIndices.Count > 2 && attempts < n * n)
{
attempts++;
bool foundEar = false;
for (int i = 0; i < vertexIndices.Count; i++)
{
int prev = vertexIndices[(i - 1 + vertexIndices.Count) % vertexIndices.Count];
int curr = vertexIndices[i];
int next = vertexIndices[(i + 1) % vertexIndices.Count];
Vector2 a = vertices[prev].ToXY();
Vector2 b = vertices[curr].ToXY();
Vector2 c = vertices[next].ToXY();
if (IsConvex(a, b, c))
{
bool isEar = true;
for (int j = 0; j < vertexIndices.Count; j++)
{
int testIndex = vertexIndices[j];
if (testIndex == prev || testIndex == curr || testIndex == next)
continue;
Vector2 p = vertices[testIndex].ToXY();
if (PointInTriangle(p, a, b, c))
{
isEar = false;
break;
}
}
if (isEar)
{
indices.Add(prev);
indices.Add(curr);
indices.Add(next);
vertexIndices.RemoveAt(i);
foundEar = true;
break;
}
}
}
if (!foundEar)
{
Debug.LogWarning($"No ear found in attempt {attempts}");
}
}
return indices;
}
// For a CCW polygon, a triangle (a,b,c) is convex if the cross product is positive.
private static bool IsConvex(Vector2 a, Vector2 b, Vector2 c)
{
float cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
return cross > 0;
}
private static bool PointInTriangle(Vector2 p, Vector2 a, Vector2 b, Vector2 c)
{
float area = 0.5f * (-b.y * c.x + a.y * (-b.x + c.x) + a.x * (b.y - c.y) + b.x * c.y);
float s = 1f / (2f * area) * (a.y * c.x - a.x * c.y + (c.y - a.y) * p.x + (a.x - c.x) * p.y);
float t = 1f / (2f * area) * (a.x * b.y - a.y * b.x + (a.y - b.y) * p.x + (b.x - a.x) * p.y);
return s >= 0 && t >= 0 && (s + t) <= 1;
}
// Convert Vector3 to Vector2 for triangulation in the XY plane.
public static Vector2 ToXY(this Vector3 v)
{
return new Vector2(v.x, v.y);
}
public static Vector2 ToXZ(this Vector3 v)
{
return new Vector2(v.x, v.z);
}
public static Vector3 FromXY(this Vector2 v)
{
return new Vector3(v.x, v.y, 0f);
}
public static Vector3 FromXZ(this Vector2 v)
{
return new Vector3(v.x, 0f, v.y);
}
public static Vector3 FlipOnAxis(this Vector3 vector, Vector3 axis)
{
axis = axis.normalized;
Vector3 projection = Vector3.Dot(vector, axis) * axis;
return vector - 2 * projection;
}
}
}