using UnityEngine; using System; using System.Collections.Generic; using System.Linq; namespace UnityEngine.ProBuilder { /// /// Represents a boundary edge where three faces meet. It holds references to: /// - The connected to this edge /// - An for finding coincident vertices on this edge /// - The previous and next winged edges in its triangle /// - The common ([coincident](../manual/gloss.html#coincident)) winged edge, called the 'opposite' edge /// /// /// ``` /// . / (face) / /// . prev / / next /// . / edge / /// . /_ _ _ _ _ _ _/ /// . |- - - - - - -| /// . | opposite | /// . | | /// . | | /// . | | /// ``` /// public sealed class WingedEdge : IEquatable { static readonly Dictionary k_OppositeEdgeDictionary = new Dictionary(); /// /// Gets the local and shared edge that this edge belongs to. /// public EdgeLookup edge { get; private set; } /// /// Gets the connected face that this wing belongs to. /// public Face face { get; private set; } /// /// Gets the WingedEdge that is connected to the `edge.y` vertex. /// public WingedEdge next { get; private set; } /// /// Gets the WingedEdge that is connected to the `edge.x` vertex. /// public WingedEdge previous { get; private set; } /// /// Gets the WingedEdge that is on the "opposite" side of this edge. /// public WingedEdge opposite { get; private set; } WingedEdge() {} /// /// Tests to see whether the specified Edge is equal to the local edge, disregarding other values. /// /// The WingedEdge to compare against. /// True if the local edges are equal, false if not. public bool Equals(WingedEdge other) { return !ReferenceEquals(other, null) && edge.local.Equals(other.edge.local); } /// /// Tests to see whether the specified object is equal to the local edge, disregarding other values. /// /// The WingedEdge to compare against. /// True if the local edges are equal, false if not. public override bool Equals(object obj) { WingedEdge be = obj as WingedEdge; if (be != null && this.Equals(be)) return true; if (obj is Edge && this.edge.local.Equals((Edge)obj)) return true; return false; } /// /// Returns the hash code for this edge. /// /// WingedEdge comparison only considers the local edge. As such, this returns the local edge hashcode. public override int GetHashCode() { return edge.local.GetHashCode(); } /// /// Returns the number of edges connected in this sequence. /// /// The number of WingedEdges found with the WingedEdge.next property. public int Count() { WingedEdge current = this; int count = 0; do { count++; current = current.next; } while (current != null && !ReferenceEquals(current, this)); return count; } /// /// Returns a string representation of the winged edge. /// /// String formatted as follows: /// /// `Common: [common]` /// /// `Local: [local]` /// /// `Opposite: [opposite]` /// /// `Face: [face]` public override string ToString() { return string.Format("Common: {0}\nLocal: {1}\nOpposite: {2}\nFace: {3}", edge.common.ToString(), edge.local.ToString(), opposite == null ? "null" : opposite.edge.ToString(), face.ToString()); } /// /// Given two adjacent triangle wings, attempt to create a single quad. /// /// /// /// internal static int[] MakeQuad(WingedEdge left, WingedEdge right) { // Both faces must be triangles in order to be considered a quad when combined if (left.Count() != 3 || right.Count() != 3) return null; EdgeLookup[] all = new EdgeLookup[6] { left.edge, left.next.edge, left.next.next.edge, right.edge, right.next.edge, right.next.next.edge }; int[] dup = new int[6]; int matches = 0; for (int i = 0; i < 3; i++) { for (int n = 3; n < 6; n++) { if (all[i].Equals(all[n])) { matches++; dup[i] = 1; dup[n] = 1; break; } } } // Edges are either not adjacent, or share more than one edge if (matches != 1) return null; int qi = 0; EdgeLookup[] edges = new EdgeLookup[4]; for (int i = 0; i < 6; i++) if (dup[i] < 1) edges[qi++] = all[i]; int[] quad = new int[4] { edges[0].local.a, edges[0].local.b, -1, -1 }; int c1 = edges[0].common.b, c2 = -1; if (edges[1].common.a == c1) { quad[2] = edges[1].local.b; c2 = edges[1].common.b; } else if (edges[2].common.a == c1) { quad[2] = edges[2].local.b; c2 = edges[2].common.b; } else if (edges[3].common.a == c1) { quad[2] = edges[3].local.b; c2 = edges[3].common.b; } if (edges[1].common.a == c2) quad[3] = edges[1].local.b; else if (edges[2].common.a == c2) quad[3] = edges[2].local.b; else if (edges[3].common.a == c2) quad[3] = edges[3].local.b; if (quad[2] == -1 || quad[3] == -1) return null; return quad; } /// /// Returns WingedEdge.previous or /// WingedEdge.next if it contains the specified common (shared) index. /// /// The common index to search next and previous for. /// The next or previous WingedEdge that contains common; or null if not found. public WingedEdge GetAdjacentEdgeWithCommonIndex(int common) { if (next.edge.common.Contains(common)) return next; else if (previous.edge.common.Contains(common)) return previous; return null; } /// /// Orders a face's edges in sequence, starting from the first edge. /// /// The source face. /// A new set of edges where each edge's Y value matches the next edge's X value. public static List SortEdgesByAdjacency(Face face) { if (face == null || face.edgesInternal == null) throw new ArgumentNullException("face"); List edges = new List(face.edgesInternal); SortEdgesByAdjacency(edges); return edges; } /// /// Sorts the specified list of edges by adjacency, such that each edge's common Y value matches the next edge's common X value. /// /// The edges to sort in-place. public static void SortEdgesByAdjacency(List edges) { if (edges == null) throw new ArgumentNullException("edges"); for (int i = 1; i < edges.Count; i++) { int want = edges[i - 1].b; for (int n = i + 1; n < edges.Count; n++) { if (edges[n].a == want || edges[n].b == want) { Edge swap = edges[n]; edges[n] = edges[i]; edges[i] = swap; } } } } /// /// Returns a dictionary of common indices and all WingedEdge values that touch each common index. /// /// The list of WingedEdges to search. /// A dictionary where each key is a common index mapped to a list of each winged edge that touches it. public static Dictionary> GetSpokes(List wings) { if (wings == null) throw new ArgumentNullException("wings"); Dictionary> spokes = new Dictionary>(); List l = null; for (int i = 0; i < wings.Count; i++) { if (spokes.TryGetValue(wings[i].edge.common.a, out l)) l.Add(wings[i]); else spokes.Add(wings[i].edge.common.a, new List() { wings[i] }); if (spokes.TryGetValue(wings[i].edge.common.b, out l)) l.Add(wings[i]); else spokes.Add(wings[i].edge.common.b, new List() { wings[i] }); } return spokes; } /// /// Given a set of winged edges and list of common indexes, attempt to create a complete path of indexes where each is connected by edge. ///
/// May be clockwise or counter-clockwise ordered, or null if no path is found. ///
/// The wings to be sorted. /// The common indexes to be sorted. /// internal static List SortCommonIndexesByAdjacency(List wings, HashSet common) { List matches = wings.Where(x => common.Contains(x.edge.common.a) && common.Contains(x.edge.common.b)).Select(y => y.edge.common).ToList(); // if edge count != index count there isn't a full perimeter if (matches.Count != common.Count) return null; SortEdgesByAdjacency(matches); return matches.ConvertAll(x => x.a); } /// /// Creates a new list of WingedEdge values from all faces on the specified ProBuilder mesh. /// /// The mesh containing the faces to analyze. /// Set this parameter to true to restrict the list to include only one WingedEdge per face. The default value is false. /// A new list of WingedEdge values gathered from the ProBuilderMesh.faces. public static List GetWingedEdges(ProBuilderMesh mesh, bool oneWingPerFace = false) { if (mesh == null) throw new ArgumentNullException("mesh"); return GetWingedEdges(mesh, mesh.facesInternal, oneWingPerFace); } /// /// Creates a new list of WingedEdge values from a specific set of faces on the specified ProBuilder mesh. /// /// The mesh containing the faces to analyze. /// The collection of faces to include in the WingedEdge list. /// Set this parameter to true to restrict the list to include only one WingedEdge per face. The default value is false. /// A new list of WingedEdge values gathered from the specified faces. public static List GetWingedEdges(ProBuilderMesh mesh, IEnumerable faces, bool oneWingPerFace = false) { if (mesh == null) throw new ArgumentNullException("mesh"); var lookup = mesh.sharedVertexLookup; List winged = new List(); k_OppositeEdgeDictionary.Clear(); foreach (Face f in faces) { List edges = SortEdgesByAdjacency(f); int edgeLength = edges.Count; WingedEdge first = null, prev = null; for (int n = 0; n < edgeLength; n++) { Edge e = edges[n]; WingedEdge w = new WingedEdge(); w.edge = new EdgeLookup(lookup[e.a], lookup[e.b], e.a, e.b); w.face = f; if (n < 1) first = w; if (n > 0) { w.previous = prev; prev.next = w; } if (n == edgeLength - 1) { w.next = first; first.previous = w; } prev = w; WingedEdge opp; if (k_OppositeEdgeDictionary.TryGetValue(w.edge.common, out opp)) { opp.opposite = w; w.opposite = opp; } else { w.opposite = null; k_OppositeEdgeDictionary.Add(w.edge.common, w); } if (!oneWingPerFace || n < 1) winged.Add(w); } } return winged; } } }