using System.Linq;
using System;
using System.Collections.Generic;
namespace UnityEngine.ProBuilder.MeshOperations
{
///
/// Provides helper functions for working with selected faces, edges, and vertices.
///
public static class ElementSelection
{
const int k_MaxHoleIterations = 2048;
///
/// Creates a list of objects where each face is connected to a specific in the ProBuilderMesh.
///
/// The ProBuilder mesh containing the edge.
/// The edge to evaluate.
/// Specify an empty list of faces for the method to fill.
public static void GetNeighborFaces(ProBuilderMesh mesh, Edge edge, List neighborFaces)
{
var lookup = mesh.sharedVertexLookup;
Edge uni = new Edge(lookup[edge.a], lookup[edge.b]);
Edge e = new Edge(0, 0);
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
Edge[] edges = mesh.facesInternal[i].edgesInternal;
for (int n = 0; n < edges.Length; n++)
{
e.a = edges[n].a;
e.b = edges[n].b;
if ((uni.a == lookup[e.a] && uni.b == lookup[e.b]) ||
(uni.a == lookup[e.b] && uni.b == lookup[e.a]))
{
neighborFaces.Add(mesh.facesInternal[i]);
break;
}
}
}
}
///
/// Returns a list of ]]> where each face is connected to the passed edge.
///
///
///
///
internal static List> GetNeighborFaces(ProBuilderMesh mesh, Edge edge)
{
List> faces = new List>();
var lookup = mesh.sharedVertexLookup;
Edge uni = new Edge(lookup[edge.a], lookup[edge.b]);
Edge e = new Edge(0, 0);
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
Edge[] edges = mesh.facesInternal[i].edgesInternal;
for (int n = 0; n < edges.Length; n++)
{
e.a = edges[n].a;
e.b = edges[n].b;
if ((uni.a == lookup[e.a] && uni.b == lookup[e.b]) ||
(uni.a == lookup[e.b] && uni.b == lookup[e.a]))
{
faces.Add(new SimpleTuple(mesh.facesInternal[i], edges[n]));
break;
}
}
}
return faces;
}
///
/// Gets all faces connected to each index taking into account shared vertices.
///
///
///
///
internal static List GetNeighborFaces(ProBuilderMesh mesh, int[] indexes)
{
var lookup = mesh.sharedVertexLookup;
List neighboring = new List();
HashSet shared = new HashSet();
foreach (int tri in indexes)
shared.Add(lookup[tri]);
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
int[] dist = mesh.facesInternal[i].distinctIndexesInternal;
for (int n = 0; n < dist.Length; n++)
{
if (shared.Contains(lookup[dist[n]]))
{
neighboring.Add(mesh.facesInternal[i]);
break;
}
}
}
return neighboring;
}
///
/// Returns a unique array of Edges connected to the passed vertex indexes.
///
///
///
///
internal static Edge[] GetConnectedEdges(ProBuilderMesh mesh, int[] indexes)
{
var lookup = mesh.sharedVertexLookup;
List connectedEdges = new List();
HashSet shared = new HashSet();
for (int i = 0; i < indexes.Length; i++)
shared.Add(lookup[indexes[i]]);
HashSet used = new HashSet();
Edge uni = new Edge(0, 0);
foreach (var face in mesh.facesInternal)
{
foreach (var edge in face.edges)
{
Edge key = new Edge(lookup[edge.a], lookup[edge.b]);
if (shared.Contains(key.a) || shared.Contains(key.b) && !used.Contains(uni))
{
connectedEdges.Add(edge);
used.Add(key);
}
}
}
return connectedEdges.ToArray();
}
///
/// Returns all the edges that are on the perimeter of this set of selected faces.
///
/// The mesh containing the faces.
/// The faces to search for perimeter edge paths.
/// A list of the edges on the perimeter of each group of adjacent faces.
public static IEnumerable GetPerimeterEdges(this ProBuilderMesh mesh, IEnumerable faces)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faces == null)
throw new ArgumentNullException("faces");
List faceEdges = faces.SelectMany(x => x.edgesInternal).ToList(); // actual edges
var sharedIndexesDictionary = mesh.sharedVertexLookup;
int edgeCount = faceEdges.Count;
// translate all face edges to universal edges
Dictionary> dup = new Dictionary>();
List list;
for (int i = 0; i < edgeCount; i++)
{
Edge uni = new Edge(sharedIndexesDictionary[faceEdges[i].a], sharedIndexesDictionary[faceEdges[i].b]);
if (dup.TryGetValue(uni, out list))
list.Add(faceEdges[i]);
else
dup.Add(uni, new List() { faceEdges[i] });
}
return dup.Where(x => x.Value.Count < 2).Select(x => x.Value[0]);
}
///
/// Returns the indexes of perimeter edges in a given element group.
///
///
///
///
internal static int[] GetPerimeterEdges(ProBuilderMesh mesh, IList edges)
{
int edgeCount = edges != null ? edges.Count : 0;
// Figure out how many connections each edge has to other edges in the selection
var universal = mesh.GetSharedVertexHandleEdges(edges).ToArray();
int[] connections = new int[universal.Length];
for (int i = 0; i < universal.Length - 1; i++)
{
for (int n = i + 1; n < universal.Length; n++)
{
if (universal[i].a == universal[n].a || universal[i].a == universal[n].b ||
universal[i].b == universal[n].a || universal[i].b == universal[n].b)
{
connections[i]++;
connections[n]++;
}
}
}
int min = Math.Min(connections);
List perimeter = new List();
for (int i = 0; i < connections.Length; i++)
{
if (connections[i] <= min)
perimeter.Add(i);
}
return perimeter.Count != edgeCount ? perimeter.ToArray() : new int[] {};
}
///
/// Returns an array of faces where each face has at least one non-shared edge.
///
///
///
///
internal static IEnumerable GetPerimeterFaces(ProBuilderMesh mesh, IEnumerable faces)
{
var lookup = mesh.sharedVertexLookup;
Dictionary> sharedEdges = new Dictionary>();
/**
* To be considered a perimeter face, at least one edge must not share
* any boundary with another face.
*/
foreach (Face face in faces)
{
foreach (Edge e in face.edgesInternal)
{
Edge edge = new Edge(lookup[e.a], lookup[e.b]);
if (sharedEdges.ContainsKey(edge))
sharedEdges[edge].Add(face);
else
sharedEdges.Add(edge, new List() { face });
}
}
return sharedEdges.Where(x => x.Value.Count < 2).Select(x => x.Value[0]).Distinct();
}
internal static int[] GetPerimeterVertices(ProBuilderMesh mesh, int[] indexes, Edge[] universal_edges_all)
{
int len = indexes.Length;
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
int[] universal = new int[len];
for (int i = 0; i < len; i++)
universal[i] = mesh.GetSharedVertexHandle(indexes[i]);
int[] connections = new int[indexes.Length];
for (int i = 0; i < indexes.Length - 1; i++)
{
for (int n = i + 1; n < indexes.Length; n++)
{
if (universal_edges_all.Contains(universal[i], universal[n]))
{
connections[i]++;
connections[n]++;
}
}
}
int min = Math.Min(connections);
List perimeter = new List();
for (int i = 0; i < len; i++)
{
if (connections[i] <= min)
perimeter.Add(i);
}
return perimeter.Count < len ? perimeter.ToArray() : new int[] {};
}
static WingedEdge EdgeRingNext(WingedEdge edge)
{
if (edge == null)
return null;
WingedEdge next = edge.next, prev = edge.previous;
int i = 0;
while (next != prev && next != edge)
{
next = next.next;
if (next == prev)
return null;
prev = prev.previous;
i++;
}
if (i % 2 == 0 || next == edge)
next = null;
return next;
}
///
/// Iterates through face edges and builds a list using the opposite edge.
///
///
///
///
internal static IEnumerable GetEdgeRing(ProBuilderMesh pb, IEnumerable edges)
{
List wings = WingedEdge.GetWingedEdges(pb);
List edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList();
edgeLookup = edgeLookup.Distinct().ToList();
Dictionary wings_dic = new Dictionary();
for (int i = 0; i < wings.Count; i++)
if (!wings_dic.ContainsKey(wings[i].edge.common))
wings_dic.Add(wings[i].edge.common, wings[i]);
HashSet used = new HashSet();
for (int i = 0, c = edgeLookup.Count; i < c; i++)
{
WingedEdge we;
if (!wings_dic.TryGetValue(edgeLookup[i].common, out we) || used.Contains(we.edge))
continue;
WingedEdge cur = we;
while (cur != null)
{
if (!used.Add(cur.edge)) break;
cur = EdgeRingNext(cur);
if (cur != null && cur.opposite != null) cur = cur.opposite;
}
cur = EdgeRingNext(we.opposite);
if (cur != null && cur.opposite != null) cur = cur.opposite;
// run in both directions
while (cur != null)
{
if (!used.Add(cur.edge)) break;
cur = EdgeRingNext(cur);
if (cur != null && cur.opposite != null) cur = cur.opposite;
}
}
return used.Select(x => x.local);
}
///
/// Iterates through face edges and builds a list using the opposite edge, iteratively.
///
/// The probuilder mesh
/// The edges already selected
/// The new selected edges
internal static IEnumerable GetEdgeRingIterative(ProBuilderMesh pb, IEnumerable edges)
{
List wings = WingedEdge.GetWingedEdges(pb);
List edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList();
edgeLookup = edgeLookup.Distinct().ToList();
Dictionary wings_dic = new Dictionary();
for (int i = 0; i < wings.Count; i++)
if (!wings_dic.ContainsKey(wings[i].edge.common))
wings_dic.Add(wings[i].edge.common, wings[i]);
HashSet used = new HashSet();
for (int i = 0, c = edgeLookup.Count; i < c; i++)
{
WingedEdge we;
if (!wings_dic.TryGetValue(edgeLookup[i].common, out we))
continue;
WingedEdge cur = we;
if (!used.Contains(cur.edge))
used.Add(cur.edge);
var next = EdgeRingNext(cur);
if (next != null && next.opposite != null && !used.Contains(next.edge))
used.Add(next.edge);
var prev = EdgeRingNext(cur.opposite);
if (prev != null && prev.opposite != null && !used.Contains(prev.edge))
used.Add(prev.edge);
}
return used.Select(x => x.local);
}
///
/// Attempts to find edges along an Edge loop.
///
/// http://wiki.blender.org/index.php/Doc:2.4/Manual/Modeling/Meshes/Selecting/Edges says:
/// First check to see if the selected element connects to only 3 other edges.
/// If the edge in question has already been added to the list, the selection ends.
/// Of the 3 edges that connect to the current edge, the ones that share a face with the current edge are eliminated
/// and the remaining edge is added to the list and is made the current edge.
///
///
///
///
///
internal static bool GetEdgeLoop(ProBuilderMesh mesh, IEnumerable edges, out Edge[] loop)
{
List wings = WingedEdge.GetWingedEdges(mesh);
IEnumerable m_edgeLookup = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup);
HashSet sources = new HashSet(m_edgeLookup);
HashSet used = new HashSet();
for (int i = 0; i < wings.Count; i++)
{
if (used.Contains(wings[i].edge) || !sources.Contains(wings[i].edge))
continue;
bool completeLoop = GetEdgeLoopInternal(wings[i], wings[i].edge.common.b, used);
// loop didn't close
if (!completeLoop)
GetEdgeLoopInternal(wings[i], wings[i].edge.common.a, used);
}
loop = used.Select(x => x.local).ToArray();
return true;
}
///
/// Attempts to find edges along an Edge loop in an iterative way
///
/// Adds two edges to the selection, one at each extremity
///
///
///
///
///
internal static bool GetEdgeLoopIterative(ProBuilderMesh mesh, IEnumerable edges, out Edge[] loop)
{
List wings = WingedEdge.GetWingedEdges(mesh);
IEnumerable m_edgeLookup = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup);
HashSet sources = new HashSet(m_edgeLookup);
HashSet used = new HashSet();
for (int i = 0; i < wings.Count; i++)
{
if (!sources.Contains(wings[i].edge))
continue;
GetEdgeLoopInternalIterative(wings[i], wings[i].edge.common, used);
}
loop = used.Select(x => x.local).ToArray();
return true;
}
static bool GetEdgeLoopInternal(WingedEdge start, int startIndex, HashSet used)
{
int ind = startIndex;
WingedEdge cur = start;
do
{
used.Add(cur.edge);
List spokes = GetSpokes(cur, ind, true).DistinctBy(x => x.edge.common).ToList();
cur = null;
if (spokes.Count == 4)
{
cur = spokes[2];
ind = cur.edge.common.a == ind ? cur.edge.common.b : cur.edge.common.a;
}
}
while (cur != null && !used.Contains(cur.edge));
return cur != null;
}
static void GetEdgeLoopInternalIterative(WingedEdge start, Edge edge, HashSet used)
{
int indA = edge.a;
int indB = edge.b;
WingedEdge cur = start;
if (!used.Contains(cur.edge))
used.Add(cur.edge);
List spokesA = GetSpokes(cur, indA, true).DistinctBy(x => x.edge.common).ToList();
List spokesB = GetSpokes(cur, indB, true).DistinctBy(x => x.edge.common).ToList();
if (spokesA.Count == 4)
{
cur = spokesA[2];
if (!used.Contains(cur.edge))
used.Add(cur.edge);
}
if (spokesB.Count == 4)
{
cur = spokesB[2];
if (!used.Contains(cur.edge))
used.Add(cur.edge);
}
}
static WingedEdge NextSpoke(WingedEdge wing, int pivot, bool opp)
{
if (opp)
return wing.opposite;
if (wing.next.edge.common.Contains(pivot))
return wing.next;
if (wing.previous.edge.common.Contains(pivot))
return wing.previous;
return null;
}
///
/// Return all edges connected to @wing with @sharedIndex as the pivot point. The first entry in the list is always the queried wing.
///
///
///
///
///
internal static List GetSpokes(WingedEdge wing, int sharedIndex, bool allowHoles = false)
{
List spokes = new List();
WingedEdge cur = wing;
bool opp = false;
do
{
// https://fogbugz.unity3d.com/f/cases/1241105/
if (spokes.Contains(cur))
return spokes;
spokes.Add(cur);
cur = NextSpoke(cur, sharedIndex, opp);
opp = !opp;
// we've looped around as far as it's gon' go
if (cur != null && cur.edge.common.Equals(wing.edge.common))
return spokes;
}
while (cur != null);
if (!allowHoles)
return null;
// if the first loop didn't come back, that means there was a hole in the geo
// do the loop again using the opposite wing
cur = wing.opposite;
opp = false;
List fragment = new List();
// if mesh is non-manifold this situation could arise
while (cur != null && !cur.edge.common.Equals(wing.edge.common))
{
fragment.Add(cur);
cur = NextSpoke(cur, sharedIndex, opp);
opp = !opp;
}
fragment.Reverse();
spokes.AddRange(fragment);
return spokes;
}
///
/// Expand the selected faces to include any face touching the perimeter edges.
/// This corresponds to the [Grow Selection](../manual/Selection_Grow.html) action.
///
/// The source mesh.
/// The faces to grow out from.
/// Specify the maximum difference (in degrees) between the normals on the selected face and those on the perimeter face.
/// The original faces selection, plus any new faces added as a result of the grow operation.
public static HashSet GrowSelection(ProBuilderMesh mesh, IEnumerable faces, float maxAngleDiff = -1f)
{
List wings = WingedEdge.GetWingedEdges(mesh, true);
HashSet source = new HashSet(faces);
HashSet neighboring = new HashSet();
Vector3 srcNormal = Vector3.zero;
bool checkAngle = maxAngleDiff > 0f;
for (int i = 0; i < wings.Count; i++)
{
if (!source.Contains(wings[i].face))
continue;
if (checkAngle)
srcNormal = Math.Normal(mesh, wings[i].face);
using (var it = new WingedEdgeEnumerator(wings[i]))
{
while (it.MoveNext())
{
var w = it.Current;
if (w.opposite != null && !source.Contains(w.opposite.face))
{
if (checkAngle)
{
Vector3 oppNormal = Math.Normal(mesh, w.opposite.face);
if (Vector3.Angle(srcNormal, oppNormal) < maxAngleDiff)
neighboring.Add(w.opposite.face);
}
else
{
neighboring.Add(w.opposite.face);
}
}
}
}
}
return neighboring;
}
static readonly Vector3 Vector3_Zero = new Vector3(0f, 0f, 0f);
internal static void Flood(WingedEdge wing, HashSet selection)
{
Flood(null, wing, Vector3_Zero, -1f, selection);
}
internal static void Flood(ProBuilderMesh pb, WingedEdge wing, Vector3 wingNrm, float maxAngle, HashSet selection)
{
WingedEdge next = wing;
do
{
WingedEdge opp = next.opposite;
if (opp != null && !selection.Contains(opp.face))
{
if (maxAngle > 0f)
{
Vector3 oppNormal = Math.Normal(pb, opp.face);
if (Vector3.Angle(wingNrm, oppNormal) < maxAngle)
{
if (selection.Add(opp.face))
Flood(pb, opp, oppNormal, maxAngle, selection);
}
}
else
{
if (selection.Add(opp.face))
Flood(pb, opp, wingNrm, maxAngle, selection);
}
}
next = next.next;
}
while (next != wing);
}
///
/// Recursively adds all faces touching any of the selected faces to the selection.
///
/// This corresponds to the [Grow Selection](../manual/Selection_Grow.html) action.
///
/// The source mesh.
/// The selected faces.
/// Specify the maximum difference (in degrees) between the normals on the selected face and those on the perimeter face.
/// The original faces selection, plus any new faces added as a result of the grow operation.
public static HashSet FloodSelection(ProBuilderMesh mesh, IList faces, float maxAngleDiff)
{
List wings = WingedEdge.GetWingedEdges(mesh, true);
HashSet source = new HashSet(faces);
HashSet flood = new HashSet();
for (int i = 0; i < wings.Count; i++)
{
if (!flood.Contains(wings[i].face) && source.Contains(wings[i].face))
{
flood.Add(wings[i].face);
Flood(mesh, wings[i], maxAngleDiff > 0f ? Math.Normal(mesh, wings[i].face) : Vector3_Zero, maxAngleDiff, flood);
}
}
return flood;
}
///
/// Finds and returns a face loop.
///
/// This is the equivalent of the [Select Face Loop](../manual/Selection_Loop_Face.html) and
/// [Select Face Ring](../manual/Selection_Ring_Face.html) actions.
///
/// The source mesh.
/// The faces to scan for loops.
/// Toggles between loop and ring. Ring and loop are arbritary with faces, so this parameter just toggles between which gets scanned first.
/// A collection of faces gathered by extending a ring or loop,
public static HashSet GetFaceLoop(ProBuilderMesh mesh, Face[] faces, bool ring = false)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faces == null)
throw new ArgumentNullException("faces");
HashSet loops = new HashSet();
List wings = WingedEdge.GetWingedEdges(mesh);
foreach (Face face in faces)
loops.UnionWith(GetFaceLoop(wings, face, ring));
return loops;
}
///
/// Finds and returns both a face ring and loop from the selected faces.
/// This is the equivalent of the [Select Face Loop](../manual/Selection_Loop_Face.html) and
/// [Select Face Ring](../manual/Selection_Ring_Face.html) actions.
///
/// The source mesh.
/// The faces to scan for ring and loops.
/// A collection of faces gathered by extending in a ring and loop.
public static HashSet GetFaceRingAndLoop(ProBuilderMesh mesh, Face[] faces)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faces == null)
throw new ArgumentNullException("faces");
HashSet loops = new HashSet();
List wings = WingedEdge.GetWingedEdges(mesh);
foreach (Face face in faces)
{
loops.UnionWith(GetFaceLoop(wings, face, true));
loops.UnionWith(GetFaceLoop(wings, face, false));
}
return loops;
}
///
/// Get a face loop or ring from a set of winged edges.
///
///
///
///
///
static HashSet GetFaceLoop(List wings, Face face, bool ring)
{
HashSet loop = new HashSet();
if (face == null)
return loop;
WingedEdge start = wings.FirstOrDefault(x => x.face == face);
if (start == null)
return loop;
if (ring)
start = start.next ?? start.previous;
for (int i = 0; i < 2; i++)
{
WingedEdge cur = start;
if (i == 1)
{
if (start.opposite != null && start.opposite.face != null)
cur = start.opposite;
else
break;
}
do
{
if (!loop.Add(cur.face))
break;
if (cur.Count() != 4)
break;
// count == 4 assures us next.next is valid, but opposite can still be null
cur = cur.next.next.opposite;
}
while (cur != null && cur.face != null);
}
return loop;
}
///
/// Find any holes touching one of the passed vertex indexes.
///
///
///
///
internal static List> FindHoles(ProBuilderMesh mesh, IEnumerable indexes)
{
HashSet common = mesh.GetSharedVertexHandles(indexes);
List> holes = new List>();
List wings = WingedEdge.GetWingedEdges(mesh);
foreach (List hole in FindHoles(wings, common))
holes.Add(hole.Select(x => x.edge.local).ToList());
return holes;
}
///
/// Find any holes touching one of the passed common indexes.
///
///
///
///
internal static List> FindHoles(List wings, HashSet common)
{
HashSet used = new HashSet();
List> holes = new List>();
for (int i = 0; i < wings.Count; i++)
{
WingedEdge c = wings[i];
// if this edge has been added to a hole already, or the edge isn't in the approved list of indexes,
// or if there's an opposite face, this edge doesn't belong to a hole. move along.
if (c.opposite != null || used.Contains(c) || !(common.Contains(c.edge.common.a) || common.Contains(c.edge.common.b)))
continue;
List hole = new List();
WingedEdge it = c;
int ind = it.edge.common.a;
int counter = 0;
while (it != null && counter++ < k_MaxHoleIterations)
{
used.Add(it);
hole.Add(it);
ind = it.edge.common.a == ind ? it.edge.common.b : it.edge.common.a;
it = FindNextEdgeInHole(it, ind);
if (it == c)
break;
}
List> splits = new List>();
// check previous wings for y == x (closed loop).
for (int n = 0; n < hole.Count; n++)
{
WingedEdge wing = hole[n];
for (int p = n - 1; p > -1; p--)
{
if (wing.edge.common.b == hole[p].edge.common.a)
{
splits.Add(new SimpleTuple(p, n));
break;
}
}
}
// create new lists from each segment
// holes paths are nested, with holes
// possibly split between multiple nested
// holes
//
// [2, 0] [5, 3]
// [0, 9] [3, 11]
// [9, 10] [11, 10]
// [10, 7] [10, 2]
// [7, 6] or with split [2, 0]
// [6, 1] nesting -> [0, 9]
// [1, 4] [9, 10]
// [4, 7] <- (y == x) [10, 7]
// [7, 8] [7, 6]
// [8, 5] [6, 1]
// [5, 3] [1, 4]
// [3, 11] [4, 7]
// [11, 10] <- (y == x) [7, 8]
// [10, 2] <- (y == x) [8, 5]
//
// paths may also contain multiple segments non-tiered
int splitCount = splits.Count;
splits.Sort((x, y) => x.item1.CompareTo(y.item1));
int[] shift = new int[splitCount];
// Debug.Log(hole.ToString("\n") + "\n" + splits.ToString("\n"));
for (int n = splitCount - 1; n > -1; n--)
{
int x = splits[n].item1, y = splits[n].item2 - shift[n];
int range = (y - x) + 1;
List section = hole.GetRange(x, range);
hole.RemoveRange(x, range);
for (int m = n - 1; m > -1; m--)
if (splits[m].item2 > splits[n].item2)
shift[m] += range;
// verify that this path has at least one index that was asked for
if (splitCount < 2 || section.Any(w => common.Contains(w.edge.common.a)) || section.Any(w => common.Contains(w.edge.common.b)))
holes.Add(section);
}
}
return holes;
}
static WingedEdge FindNextEdgeInHole(WingedEdge wing, int common)
{
WingedEdge next = wing.GetAdjacentEdgeWithCommonIndex(common);
int counter = 0;
while (next != null && next != wing && counter++ < k_MaxHoleIterations)
{
if (next.opposite == null)
return next;
next = next.opposite.GetAdjacentEdgeWithCommonIndex(common);
}
return null;
}
}
}