using AssetRipper.Numerics; using System.Numerics; namespace AssetRipper.SourceGenerated.Extensions; public sealed class MeshOutlineGenerator { private readonly struct Outline { private readonly record struct Outside(int Triangle, int Member); public Outline(IReadOnlyList triangles, int startTriangle) { m_triangles = triangles ?? throw new ArgumentNullException(nameof(triangles)); GenerateIndexes(startTriangle); GenerateOutsiders(); } public void GenerateOutline() { m_outline.Clear(); Outside outsider = m_outsiders[0]; Vector3i tri = m_triangles[outsider.Triangle]; int first = tri.GetValueByMember(outsider.Member); int second = tri.GetValueByMember(outsider.Member + 1); int startTriIndex = outsider.Triangle; m_outline.Add(first); m_outline.Add(second); Vector3i lastTri = tri; int lastMember = outsider.Member + 1; int lastVertex = lastTri.GetValueByMember(outsider.Member); int lastTriIndex = outsider.Triangle; while (true) { if (GetNextOutsider(lastTri, lastMember, out outsider)) { lastTri = m_triangles[outsider.Triangle]; lastMember = outsider.Member + 1; lastVertex = lastTri.GetValueByMember(outsider.Member); lastTriIndex = outsider.Triangle; } else { lastMember++; if (lastTri.GetValueByMember(lastMember) == lastVertex) { if (lastVertex != first) { break; } } } int nextVertex = lastTri.GetValueByMember(lastMember); if (nextVertex == first) { break; } m_outline.Add(nextVertex); } } public bool Contains(int triIndex) => m_indexes.Contains(triIndex); private int GetEdgeMask(Vector3i triangle, IReadOnlyList indexes) { int edgeMask = 0; for (int i = 0; i < indexes.Count; i++) { Vector3i check = m_triangles[indexes[i]]; if (check == triangle) { continue; } int edge = GetNeighborEdge(triangle, check); if (edge == 0) { continue; } edgeMask |= edge; } return edgeMask; } private static int GetNeighborEdge(Vector3i tri1, Vector3i tri2) { if (IsNeighbors(tri1.X, tri1.Y, tri2)) { return 1 << 0; } if (IsNeighbors(tri1.Y, tri1.Z, tri2)) { return 1 << 1; } if (IsNeighbors(tri1.Z, tri1.X, tri2)) { return 1 << 2; } return 0; } private static bool IsNeighbors(int a, int b, Vector3i tri2) { if (a == tri2.X && b == tri2.Z) { return true; } if (b == tri2.Y && a == tri2.Z) { return true; } if (b == tri2.X && a == tri2.Y) { return true; } return false; } private void GenerateIndexes(int startTriangle) { List indexes = new List(); indexes.Add(startTriangle); m_indexes.Add(startTriangle); for (int i = 0; i < indexes.Count; i++) { int index = indexes[i]; Vector3i triangle = m_triangles[index]; //int edgeMask = GetEdgeMask(triangle, indexes); for (int j = 0; j < m_triangles.Count; j++) { if (m_indexes.Contains(j)) { continue; } Vector3i check = m_triangles[j]; int edge = GetNeighborEdge(triangle, check); if (edge == 0) { continue; } // allow only one neighbor per edge //if ((edgeMask & edge) != 0) { //continue; } indexes.Add(j); m_indexes.Add(j); //edgeMask |= edge; } } } private void GenerateOutsiders() { foreach (int index in m_indexes) { if (GenerateOutsider(index, out Outside outsider)) { m_outsiders.Add(outsider); } } } private bool GenerateOutsider(int index, out Outside outsider) { Vector3i triangle = m_triangles[index]; int neighborCount = 0; bool xy = true; bool yz = true; bool zx = true; outsider = new(); foreach (int checkIndex in m_indexes) { if (checkIndex == index) { continue; } Vector3i check = m_triangles[checkIndex]; if (check.ContainsValue(triangle.X)) { if (check.ContainsValue(triangle.Y)) { xy = false; if (++neighborCount == 3) { return false; } } else if (check.ContainsValue(triangle.Z)) { zx = false; if (++neighborCount == 3) { return false; } } } else if (check.ContainsValue(triangle.Y) && check.ContainsValue(triangle.Z)) { yz = false; if (++neighborCount == 3) { return false; } } } int vertex = xy ? zx ? 2 : 0 : yz ? 1 : 2; outsider = new Outside(index, vertex); return true; } private bool GetNextOutsider(Vector3i triangle, int member, out Outside result) { int vertex = triangle.GetValueByMember(member); foreach (Outside outsider in m_outsiders) { Vector3i check = m_triangles[outsider.Triangle]; if (check.GetValueByMember(outsider.Member) == vertex) { if (IsConnectedNeighbors(triangle, check, vertex)) { result = outsider; return true; } } } result = default; return false; } private bool GetNextNeighbor(Vector3i tri, int vertex, out Vector3i result) { int member = tri.GetMemberByValue(vertex); int nextVertex = tri.GetValueByMember(member + 1); foreach (int index in m_indexes) { Vector3i check = m_triangles[index]; if (check.ContainsValue(vertex) && check.ContainsValue(nextVertex)) { if (check != tri) { result = check; return true; } } } result = default; return false; } private bool IsConnectedNeighbors(Vector3i tri1, Vector3i tri2, int vertex) { if (GetNeighborEdge(tri1, tri2) != 0) { return true; } Vector3i next = tri1; while (GetNextNeighbor(next, vertex, out next)) { if (next == tri2) { return true; } } return false; } public int TriangleCount => m_indexes.Count; public IReadOnlyList GeneratedOutline => m_outline; private readonly IReadOnlyList m_triangles; private readonly HashSet m_indexes = new(); private readonly List m_outline = new(); private readonly List m_outsiders = new(); } public MeshOutlineGenerator(IReadOnlyList vertices, IReadOnlyList triangles) { ArgumentNullException.ThrowIfNull(vertices); ArgumentNullException.ThrowIfNull(triangles); m_vertices = vertices; foreach (Vector3i triangle in triangles) { if (IsValidTriangle(triangle)) { m_triangles.Add(triangle); } } } public List GenerateOutlines() { List outlines = new(); for (int i = 0; i < m_triangles.Count; i++) { bool isKnown = false; for (int j = 0; j < outlines.Count; j++) { if (outlines[j].Contains(i)) { isKnown = true; break; } } if (isKnown) { continue; } Outline outline = new Outline(m_triangles, i); outline.GenerateOutline(); outlines.Add(outline); } List result = new(); List resultLine = new(); for (int i = 0; i < outlines.Count; i++) { resultLine.Clear(); Outline outline = outlines[i]; for (int j = 0; j < outline.GeneratedOutline.Count; j++) { int vertex = outline.GeneratedOutline[j]; // commented out by mafaca // include outlines that has common vertex with current outline /*for (int k = i + 1; k < outlines.Count; k++) { Outline nextOutline = outlines[k]; int index = nextOutline.GeneratedOutline.IndexOf(vertex); if (index != -1) { for (int l = index; l < nextOutline.GeneratedOutline.Count; l++) { int nextVertex = nextOutline.GeneratedOutline[l]; resultLine.Add(m_vertices[nextVertex].AsVector2()); } for (int m = 0; m < index; m++) { int nextVertex = nextOutline.GeneratedOutline[m]; resultLine.Add(m_vertices[nextVertex].AsVector2()); } outlines.RemoveAt(k--); } }*/ resultLine.Add(m_vertices[vertex].AsVector2()); } result.Add(resultLine.ToArray()); } return result; } private static bool IsValidTriangle(Vector3i triangle) { return triangle.X != triangle.Y && triangle.X != triangle.Z && triangle.Y != triangle.Z; } private readonly IReadOnlyList m_vertices; private readonly List m_triangles = new(); }