/**
	 *	
	 */
	public static bool ConnectEdges(this pb_Object pb, List<EdgeConnection> edgeConnectionsUnfiltered, out pb_Face[] faces)
	{
		// first, remove any junk connections.  faces with less than two edges confuse this method.
		List<EdgeConnection> edgeConnections = new List<EdgeConnection>();
		foreach(EdgeConnection ec in edgeConnectionsUnfiltered)
			if(ec.isValid)
				edgeConnections.Add(ec);

		int len = edgeConnections.Count;

		if(len < 1)
		{
			Debug.LogWarning("No valid split paths found.  This is most likely because you are attempting to split edges that do belong to the same face, or do not have more than one edge selected.  This is not currently supported, sorry!");
			faces = null;
			return false;
		}


		Vector3[] vertices = pb.vertices;

		List<pb_Face> successfullySplitFaces = new List<pb_Face>();

		List<pb_Face> all_splitFaces = new List<pb_Face>();
		List<Vector3[]> all_splitVertices = new List<Vector3[]>();
		List<int[]> all_splitSharedIndices = new List<int[]>();
		bool[] success = new bool[len];

		// use a nullable type because in order for the adjacent face triangulation
		// code to work, it needs to know what dangling vert belongs to which edge, 
		// if we out a vector3[] with each index corresponding to the passed edges
		// in EdgeConnection, it's easy to maintain the relationship.
		Vector3?[][] danglingVertices = new Vector3?[len][];	

		int i = 0;
		foreach(EdgeConnection fc in edgeConnections)
		{	
			pb_Face[] splitFaces = null;
			Vector3[][] splitVertices = null;
			int[][] splitSharedIndices = null;
	
			if( fc.edges.Count < 3 )
			{
				Vector3 edgeACen = (vertices[fc.edges[0].x] + vertices[fc.edges[0].y]) / 2f; 
				Vector3 edgeBCen = (vertices[fc.edges[1].x] + vertices[fc.edges[1].y]) / 2f;
				danglingVertices[i] = new Vector3?[2] { edgeACen, edgeBCen };
				success[i] = SplitFace_Internal(new SplitSelection(pb, fc.face, edgeACen, edgeBCen, false, false, -1, -1),
					out splitFaces,
					out splitVertices, 
					out splitSharedIndices);

				if(success[i])
					successfullySplitFaces.Add(fc.face);
			}
			else
			{
				Vector3?[] appendedVertices = null;
				success[i] = SubdivideFace_Internal(pb, fc,
					out appendedVertices,
					out splitFaces,
					out splitVertices,
					out splitSharedIndices);
	
				if(success[i])
					successfullySplitFaces.Add(fc.face);
	
				danglingVertices[i] = appendedVertices;
			}

			if(success[i])
			{
				int texGroup = fc.face.textureGroup < 0 ? pb.UnusedTextureGroup(i+1) : fc.face.textureGroup;
				
				for(int j = 0; j < splitFaces.Length; j++)
				{
					splitFaces[j].textureGroup = texGroup;
					all_splitFaces.Add(splitFaces[j]);
					all_splitVertices.Add(splitVertices[j]);
					all_splitSharedIndices.Add(splitSharedIndices[j]);
				}
			}

			i++;
		}

		/**
		 *	Figure out which faces need to be re-triangulated
		 */
		pb_Edge[][] tedges = new pb_Edge[edgeConnections.Count][];
		int n = 0;
		for(i = 0; i < edgeConnections.Count; i++)
			tedges[n++] = edgeConnections[i].edges.ToArray();

		List<pb_Face>[][] allConnects = pbMeshUtils.GetConnectedFacesJagged(pb, tedges);		

		Dictionary<pb_Face, List<Vector3>> addVertex = new Dictionary<pb_Face, List<Vector3>>();
		List<pb_Face> temp = new List<pb_Face>();
		for(int j = 0; j < edgeConnections.Count; j++)
		{
			if(!success[j]) continue;

			// check that this edge has a buddy that it welded it's new vertex to, and if not,
			// create one
			for(i = 0; i < edgeConnections[j].edges.Count; i++)
			{
				if(danglingVertices[j][i] == null) 
					continue;

				List<pb_Face> connected = allConnects[j][i];

				foreach(pb_Face face in connected)
				{
					int ind = successfullySplitFaces.IndexOf(face);

					if(ind < 0)
					{
						if(addVertex.ContainsKey(face))
							addVertex[face].Add((Vector3)danglingVertices[j][i]);
						else
						{
							temp.Add(face);
							addVertex.Add(face, new List<Vector3>(1) { (Vector3)danglingVertices[j][i] });
						}
					}
				}
			}
		}
		
		pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray());
		
		List<pb_Face> triangulatedFaces = new List<pb_Face>();
		foreach(KeyValuePair<pb_Face, List<Vector3>> add in addVertex)
		{
			pb_Face newFace;
			if( pb.AppendVerticesToFace(add.Key, add.Value, out newFace) )
				triangulatedFaces.Add(newFace);
			else
				Debug.LogError("Mesh re-triangulation failed.  Specifically, AppendVerticesToFace(" + add.Key + " : " + add.Value.ToFormattedString(", "));
		}

		// Re-triangulate any faces left with dangling verts at edges
		// Weld verts, including those added in re-triangu
		int[] splitFaceTris = pb_Face.AllTriangles(appendedFaces);
		int[] triangulatedFaceTris = pb_Face.AllTriangles(triangulatedFaces);
		int[] allModifiedTris = new int[splitFaceTris.Length + triangulatedFaceTris.Length];
		System.Array.Copy(splitFaceTris, 0, allModifiedTris, 0, splitFaceTris.Length);
		System.Array.Copy(triangulatedFaceTris, 0, allModifiedTris, splitFaceTris.Length, triangulatedFaceTris.Length);
		
		pb.WeldVertices(allModifiedTris, Mathf.Epsilon);
		
		// Now that we're done screwing with geo, delete all the old faces (that were successfully split)		
		pb.DeleteFaces( successfullySplitFaces.ToArray() );
		faces = appendedFaces;
		return true;
	}
	/**
	 *	Splits face per vertex.
	 *	Todo - Could implement more sanity checks - namely testing for edges before sending to Split_Internal.  However,
	 *	the split method is smart enough to fail on those cases, so ignore for now.
	 */
	public static bool ConnectVertices(this pb_Object pb, List<VertexConnection> vertexConnectionsUnfiltered, out pb_Face[] faces)
	{
		List<VertexConnection> vertexConnections = new List<VertexConnection>();
		List<int> inds = new List<int>();

		int i = 0;

		for(i = 0; i < vertexConnectionsUnfiltered.Count; i++)
		{
			VertexConnection vc = vertexConnectionsUnfiltered[i];
			vc.indices = vc.indices.Distinct().ToList();

			if(vc.isValid) 
			{
				inds.AddRange(vc.indices);
				vertexConnections.Add(vc);
			}
		}

		if(vertexConnections.Count < 1)
		{
			faces = null;
			return false;
		}
		
		int len = vertexConnections.Count;
		List<pb_Face> successfullySplitFaces = new List<pb_Face>();
		List<pb_Face> all_splitFaces = new List<pb_Face>();
		List<Vector3[]> all_splitVertices = new List<Vector3[]>();
		List<int[]> all_splitSharedIndices = new List<int[]>();
		bool[] success = new bool[len];

		pb_IntArray[] sharedIndices = pb.sharedIndices;

		i = 0;
		foreach(VertexConnection vc in vertexConnections)
		{
			pb_Face[] splitFaces = null;
			Vector3[][] splitVertices = null;
			int[][] splitSharedIndices = null;
	
			if( vc.indices.Count < 3 )
			{
				int indA = vc.face.indices.IndexOf(vc.indices[0], sharedIndices);
				int indB = vc.face.indices.IndexOf(vc.indices[1], sharedIndices);
				
				if(indA < 0 || indB < 0)
				{
					success[i] = false;
					continue;
				}

				indA = vc.face.indices[indA];
				indB = vc.face.indices[indB];

				success[i] = SplitFace_Internal(new SplitSelection(pb, vc.face, pb.vertices[indA], pb.vertices[indB], true, true, indA, indB),
					out splitFaces,
					out splitVertices, 
					out splitSharedIndices);

				if(success[i])
					successfullySplitFaces.Add(vc.face);
			}
			else
			{
				success[i] = PokeFace_Internal(pb, vc.face, vc.indices.ToArray(),
					out splitFaces,
					out splitVertices, 
					out splitSharedIndices);

				if(success[i])
					successfullySplitFaces.Add(vc.face);
			}

			if(success[i])
			{
				int texGroup = pb.UnusedTextureGroup(i+1);

				for(int j = 0; j < splitFaces.Length; j++)
				{
					splitFaces[j].textureGroup = texGroup;
					all_splitFaces.Add(splitFaces[j]);
					all_splitVertices.Add(splitVertices[j]);
					all_splitSharedIndices.Add(splitSharedIndices[j]);
				}
			}

			i++;
		}

		if(all_splitFaces.Count < 1)
		{
			faces = null;
			return false;
		}

		pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray());
		inds.AddRange(pb_Face.AllTriangles(appendedFaces));
		
		pb.WeldVertices(inds.ToArray(), Mathf.Epsilon);

		pb.DeleteFaces(successfullySplitFaces.ToArray());

		faces = appendedFaces;
		return true;
	}
Exemplo n.º 3
0
	private static bool ConnectEdges(this pb_Object pb, List<pb_EdgeConnection> pb_edgeConnectionsUnfiltered, out pb_Face[] faces)
	{
		List<pb_EdgeConnection> pb_edgeConnections = new List<pb_EdgeConnection>();
		foreach(pb_EdgeConnection ec in pb_edgeConnectionsUnfiltered)
			if(ec.isValid)
				pb_edgeConnections.Add(ec);

		int len = pb_edgeConnections.Count;

		if(len < 1)
		{
			faces = null;
			return false;
		}

		Vector3[] vertices = pb.vertices;
		Color[] colors = pb.colors;

		List<pb_Face> successfullySplitFaces = new List<pb_Face>();

		List<pb_Face> all_splitFaces 		= new List<pb_Face>();
		List<Vector3[]> all_splitVertices 	= new List<Vector3[]>();
		List<Color[]> all_splitColors 		= new List<Color[]>();
		List<Vector2[]> all_splitUVs	 	= new List<Vector2[]>();
		List<int[]> all_splitSharedIndices	= new List<int[]>();
		bool[] success 						= new bool[len];

		// use a nullable type because in order for the adjacent face triangulation
		// code to work, it needs to know what dangling vert belongs to which edge, 
		// if we out a vector3[] with each index corresponding to the passed edges
		// in pb_EdgeConnection, it's easy to maintain the relationship.
		DanglingVertex?[][] danglingVertices = new DanglingVertex?[len][];	

		// profiler.BeginSample("foreach(edge connection)");
		int i = 0;
		foreach(pb_EdgeConnection fc in pb_edgeConnections)
		{	
			pb_Face[] splitFaces 		= null;
			Vector3[][] splitVertices 	= null;
			Color[][] splitColors  		= null;
			Vector2[][] splitUVs 		= null;
			int[][] splitSharedIndices 	= null;
	
			if( fc.edges.Count < 3 )
			{
				Vector3 edgeACen = (vertices[fc.edges[0].x] + vertices[fc.edges[0].y]) / 2f; 
				Vector3 edgeBCen = (vertices[fc.edges[1].x] + vertices[fc.edges[1].y]) / 2f;

				Color cola = (colors[fc.edges[0].x] + colors[fc.edges[0].y]) / 2f;
				Color colb = (colors[fc.edges[1].x] + colors[fc.edges[1].y]) / 2f;

				danglingVertices[i] = new DanglingVertex?[2] { new DanglingVertex(edgeACen, cola), new DanglingVertex(edgeBCen, colb) };

				success[i] = SplitFace_Internal(
					new SplitSelection(pb, fc.face, edgeACen, edgeBCen, cola, colb, false, false, new int[]{fc.edges[0].x, fc.edges[0].y}, new int[]{fc.edges[1].x, fc.edges[1].y}),
					out splitFaces,
					out splitVertices, 
					out splitColors,
					out splitUVs,
					out splitSharedIndices);
				
				if(success[i])
					successfullySplitFaces.Add(fc.face);
			}
			else
			{
				DanglingVertex?[] appendedVertices = null;

				success[i] = SubdivideFace_Internal(pb, fc,
					out appendedVertices,
					out splitFaces,
					out splitVertices,
					out splitColors,
					out splitUVs,
					out splitSharedIndices);

				if(success[i])
					successfullySplitFaces.Add(fc.face);
	
				danglingVertices[i] = appendedVertices;
			}

			if(success[i])
			{
				int texGroup = fc.face.textureGroup < 0 ? pb.UnusedTextureGroup(i+1) : fc.face.textureGroup;
				
				for(int j = 0; j < splitFaces.Length; j++)
				{
					splitFaces[j].textureGroup = texGroup;
					all_splitFaces.Add(splitFaces[j]);
					all_splitVertices.Add(splitVertices[j]);
					all_splitColors.Add(splitColors[j]);
					all_splitUVs.Add(splitUVs[j]);
					all_splitSharedIndices.Add(splitSharedIndices[j]);
				}
			}

			i++;
		}
		// profiler.EndSample();


		// profiler.BeginSample("Retrianguate");
		/**
		 *	Figure out which faces need to be re-triangulated
		 */
		pb_Edge[][] tedges = new pb_Edge[pb_edgeConnections.Count][];
		int n = 0;
		for(i = 0; i < pb_edgeConnections.Count; i++)
			tedges[n++] = pb_edgeConnections[i].edges.ToArray();

		List<pb_Face>[][] allConnects = pbMeshUtils.GetNeighborFacesJagged(pb, tedges);		


		Dictionary<pb_Face, List<DanglingVertex>> addVertex = new Dictionary<pb_Face, List<DanglingVertex>>();
		List<pb_Face> temp = new List<pb_Face>();
		for(int j = 0; j < pb_edgeConnections.Count; j++)
		{
			if(!success[j]) continue;

			// check that this edge has a buddy that it welded it's new vertex to, and if not,
			// create one
			for(i = 0; i < pb_edgeConnections[j].edges.Count; i++)
			{
				if(danglingVertices[j][i] == null) 
					continue;

				List<pb_Face> connected = allConnects[j][i];

				foreach(pb_Face face in connected)
				{
					int ind = successfullySplitFaces.IndexOf(face);

					if(ind < 0)
					{
						if(addVertex.ContainsKey(face))
							addVertex[face].Add( (DanglingVertex)danglingVertices[j][i] );
						else
						{
							temp.Add(face);
							addVertex.Add(face, new List<DanglingVertex>(1) { (DanglingVertex)danglingVertices[j][i] });
						}
					}
				}
			}
		}
		// profiler.EndSample();

		// profiler.BeginSample("Append vertices to faces");
		pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitColors.ToArray(), all_splitUVs.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray());
		
		List<pb_Face> triangulatedFaces = new List<pb_Face>();
		foreach(KeyValuePair<pb_Face, List<DanglingVertex>> add in addVertex)
		{
			pb_Face newFace;

			if( pb.AppendVerticesToFace(add.Key, add.Value.Select(x => x.position).ToArray(), add.Value.Select(x => x.color).ToArray(), out newFace) )
				triangulatedFaces.Add(newFace);
			else
				Debug.LogError("Mesh re-triangulation failed.");//  Specifically, AppendVerticesToFace(" + add.Key + " : " + add.Value.ToFormattedString(", "));
		}
		// profiler.EndSample();

		// profiler.BeginSample("rebuild mesh");

		// Re-triangulate any faces left with dangling verts at edges
		// Weld verts, including those added in re-triangu
		int[] splitFaceTris = pb_Face.AllTriangles(appendedFaces);
		int[] triangulatedFaceTris = pb_Face.AllTriangles(triangulatedFaces);
		int[] allModifiedTris = new int[splitFaceTris.Length + triangulatedFaceTris.Length];
		
		System.Array.Copy(splitFaceTris, 0, allModifiedTris, 0, splitFaceTris.Length);
		System.Array.Copy(triangulatedFaceTris, 0, allModifiedTris, splitFaceTris.Length, triangulatedFaceTris.Length);
		
		// safe to assume that we probably didn't delete anything :/
		int[] welds;

		// profiler.BeginSample("weld vertices");
		pb.WeldVertices(allModifiedTris, Mathf.Epsilon, out welds);

		// profiler.EndSample();
		// pb.SetSharedIndices( pb_IntArrayUtility.ExtractSharedIndices(pb.vertices) );

		// Now that we're done screwing with geo, delete all the old faces (that were successfully split)		
		// profiler.BeginSample("delete faces");
		pb.DeleteFaces( successfullySplitFaces.ToArray() );
		faces = appendedFaces;
		// profiler.EndSample();
		// profiler.EndSample();
		// profiler.EndSample();
		// Debug.Log(profiler.ToString());

		return true;
	}
Exemplo n.º 4
0
	/**
	 *	Splits face per vertex.
	 *	Todo - Could implement more sanity checks - namely testing for edges before sending to Split_Internal.  However,
	 *	the split method is smart enough to fail on those cases, so ignore for now.
	 */
	public static bool ConnectVertices(this pb_Object pb, List<pb_VertexConnection> vertexConnectionsUnfiltered, out int[] triangles)
	{
		List<pb_VertexConnection> vertexConnections = new List<pb_VertexConnection>();
		List<int> inds = new List<int>();

		int i = 0;

		for(i = 0; i < vertexConnectionsUnfiltered.Count; i++)
		{
			pb_VertexConnection vc = vertexConnectionsUnfiltered[i];
			vc.indices = vc.indices.Distinct().ToList();

			if(vc.isValid) 
			{
				inds.AddRange(vc.indices);
				vertexConnections.Add(vc);
			}
		}

		if(vertexConnections.Count < 1)
		{
			triangles = null;
			return false;
		}

		List<Vector3> selectedVertices = pb.GetVertices( pb_VertexConnection.AllTriangles(vertexConnections) );

		int len = vertexConnections.Count;

		// new faces will be built from successfull split ops
		List<pb_Face> successfullySplitFaces = new List<pb_Face>();
		List<pb_Face> all_splitFaces = new List<pb_Face>();

		List<Vector3[]> all_splitVertices = new List<Vector3[]>();
		List<Color[]> all_splitColors = new List<Color[]>();
		List<Vector2[]> all_splitUVs = new List<Vector2[]>();

		List<int[]> all_splitSharedIndices = new List<int[]>();
		bool[] success = new bool[len];

		pb_IntArray[] sharedIndices = pb.sharedIndices;

		i = 0;
		foreach(pb_VertexConnection vc in vertexConnections)
		{
			pb_Face[] splitFaces = null;
			Vector3[][] splitVertices = null;
			Color[][] splitColors = null;
			Vector2[][] splitUVs;
			int[][] splitSharedIndices = null;
	
			if( vc.indices.Count < 3 )
			{
				int indA = vc.face.indices.IndexOf(vc.indices[0], sharedIndices);
				int indB = vc.face.indices.IndexOf(vc.indices[1], sharedIndices);
				
				if(indA < 0 || indB < 0)
				{
					success[i] = false;
					continue;
				}

				indA = vc.face.indices[indA];
				indB = vc.face.indices[indB];

				success[i] = SplitFace_Internal(
					new SplitSelection(pb, vc.face, pb.vertices[indA], pb.vertices[indB], pb.colors[indA], pb.colors[indB], true, true, new int[] {indA}, new int[] {indB}),
					out splitFaces,
					out splitVertices, 
					out splitColors,
					out splitUVs,
					out splitSharedIndices);

				if(success[i])
					successfullySplitFaces.Add(vc.face);
			}
			else
			{
				Vector3 pokedVertex;

				success[i] = PokeFace_Internal(pb, vc.face, vc.indices.ToArray(),
					out pokedVertex,
					out splitFaces,
					out splitVertices,
					out splitColors, 
					out splitUVs, 
					out splitSharedIndices);

				if(success[i])
				{
					selectedVertices.Add(pokedVertex);
					successfullySplitFaces.Add(vc.face);
				}
			}

			if(success[i])
			{
				int texGroup = pb.UnusedTextureGroup(i+1);

				for(int j = 0; j < splitFaces.Length; j++)
				{
					splitFaces[j].textureGroup = texGroup;

					all_splitFaces.Add(splitFaces[j]);
					all_splitVertices.Add(splitVertices[j]);
					all_splitColors.Add(splitColors[j]);
					all_splitUVs.Add(splitUVs[j]);

					all_splitSharedIndices.Add(splitSharedIndices[j]);
				}
			}

			i++;
		}

		if(all_splitFaces.Count < 1)
		{
			triangles = null;
			return false;
		}

		pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(),
		                                         all_splitColors.ToArray(),
		                                         all_splitUVs.ToArray(),
		                                         all_splitFaces.ToArray(),
		                                         all_splitSharedIndices.ToArray());

		inds.AddRange(pb_Face.AllTriangles(appendedFaces));
		
		int[] welds;
		pb.WeldVertices(inds.ToArray(), Mathf.Epsilon, out welds);

		pb.DeleteFaces(successfullySplitFaces.ToArray());
		
		List<int> seltris = new List<int>();
		for(i = 0; i < selectedVertices.Count; i++)
		{
			int ind = System.Array.IndexOf(pb.vertices, selectedVertices[i]);
			if(ind > -1)	
				seltris.Add(ind);
		}

		triangles = seltris.Distinct().ToArray();
		return true;
	}
Exemplo n.º 5
0
	/**
	 * Extrudes passed faces on their normal axis using extrudeDistance.
	 */
	public static bool Extrude(this pb_Object pb, pb_Face[] faces, float extrudeDistance, bool extrudeAsGroup, out pb_Face[] appendedFaces)
	{
		appendedFaces = null;

		if(faces == null || faces.Length < 1)
			return false;

		pb_IntArray[] sharedIndices = pb.GetSharedIndices();
		Dictionary<int, int> lookup = sharedIndices.ToDictionary();

		Vector3[] localVerts = pb.vertices;

		pb_Edge[][] perimeterEdges = extrudeAsGroup ? new pb_Edge[1][] { pbMeshUtils.GetPerimeterEdges(pb, lookup, faces).ToArray() } : faces.Select(x => x.edges).ToArray();

		if(perimeterEdges == null || perimeterEdges.Length < 1 || (extrudeAsGroup && perimeterEdges[0].Length < 3))
		{
			Debug.LogWarning("No perimeter edges found.  Try deselecting and reselecting this object and trying again.");
			return false;
		}

		pb_Face[][] edgeFaces = new pb_Face[perimeterEdges.Length][];	// can't assume faces and perimiter edges will be 1:1 - so calculate perimeters then extract face information
		int[][] allEdgeIndices = new int[perimeterEdges.Length][];
		int c = 0;

		for(int i = 0; i < perimeterEdges.Length; i++)
		{
			c = 0;
			allEdgeIndices[i] = new int[perimeterEdges[i].Length * 2];
			edgeFaces[i] = new pb_Face[perimeterEdges[i].Length];

			for(int n = 0; n < perimeterEdges[i].Length; n++)
			{
				// gets the faces associated with each perimeter edge
				foreach(pb_Face face in faces)
				{
					if(face.edges.Contains(perimeterEdges[i][n]))
					{
						edgeFaces[i][n] = face;
						break;
					}
				}

				allEdgeIndices[i][c++] = perimeterEdges[i][n].x;
				allEdgeIndices[i][c++] = perimeterEdges[i][n].y;
			}
		}

		List<pb_Edge>[] extrudedIndices = new List<pb_Edge>[perimeterEdges.Length];
		Vector3[] normals = pb.msh.normals;

		List<Vector3[]> append_vertices = new List<Vector3[]>();
		List<Color[]> append_color = new List<Color[]>();
		List<Vector2[]> append_uv = new List<Vector2[]>();
		List<pb_Face> append_face = new List<pb_Face>();
		List<int[]> append_shared = new List<int[]>();

		/// build out new faces around edges
		
		for(int i = 0; i < perimeterEdges.Length; i++)
		{
			extrudedIndices[i] = new List<pb_Edge>();

			for(int n = 0; n < perimeterEdges[i].Length; n++)
			{
				pb_Edge edge = perimeterEdges[i][n];
				pb_Face face = edgeFaces[i][n];

				// Averages the normals using only vertices that are on the edge
				Vector3 xnorm = Vector3.zero;
				Vector3 ynorm = Vector3.zero;

				// don't bother getting vertex normals if not auto-extruding
				if( Mathf.Abs(extrudeDistance) > Mathf.Epsilon)
				{
					if( !extrudeAsGroup )
					{
						xnorm = pb_Math.Normal( localVerts[face.indices[0]], localVerts[face.indices[1]], localVerts[face.indices[2]] );
						ynorm = xnorm;					
					}
					else
					{
						xnorm = Norm(sharedIndices[lookup[edge.x]], allEdgeIndices[i], normals );
						ynorm = Norm(sharedIndices[lookup[edge.y]], allEdgeIndices[i], normals );
					}
				}

				int x_sharedIndex = lookup[edge.x];
				int y_sharedIndex = lookup[edge.y];

				// this could be condensed to a single call with an array of new faces
				append_vertices.Add( new Vector3[]
					{
						localVerts [ edge.x ],
						localVerts [ edge.y ],
						localVerts [ edge.x ] + xnorm.normalized * extrudeDistance,
						localVerts [ edge.y ] + ynorm.normalized * extrudeDistance
					});

				append_color.Add( new Color[]
					{	
						pb.colors[ edge.x ],
						pb.colors[ edge.y ],
						pb.colors[ edge.x ],
						pb.colors[ edge.y ]
					});

				append_uv.Add( new Vector2[4] );

				append_face.Add( new pb_Face( 
						new int[6] {0, 1, 2, 1, 3, 2},			// indices
						face.material,							// material
						new pb_UV(face.uv),						// UV material
						face.smoothingGroup,					// smoothing group
						-1,										// texture group
						-1,										// uv element group
						false)									// manualUV flag
						);

				append_shared.Add( new int[4]
					{
						x_sharedIndex,
						y_sharedIndex,
						-1,
						-1 
					});

				extrudedIndices[i].Add(new pb_Edge(x_sharedIndex, -1));
				extrudedIndices[i].Add(new pb_Edge(y_sharedIndex, -1));
			}
		}

		appendedFaces = pb.AppendFaces( append_vertices.ToArray(), append_color.ToArray(), append_uv.ToArray(), append_face.ToArray(), append_shared.ToArray() );

		// x = shared index, y = triangle (only known once faces are appended to pb_Object)
		for(int i = 0, f = 0; i < extrudedIndices.Length; i++)
		{
			for(int n = 0; n < extrudedIndices[i].Count; n+=2)
			{
				extrudedIndices[i][n+0].y = appendedFaces[f].indices[2];
				extrudedIndices[i][n+1].y = appendedFaces[f++].indices[4];
			}
		}

		pb_IntArray[] si = pb.sharedIndices;	// leave the sharedIndices copy alone since we need the un-altered version later
		Dictionary<int, int> welds = si.ToDictionary();

		// Weld side-wall top vertices together, both grouped and non-grouped need this.
		for(int f = 0; f < extrudedIndices.Length; f++)
		{
			for(int i = 0; i < extrudedIndices[f].Count-1; i++)
			{
				int val = extrudedIndices[f][i].x;
				for(int n = i+1; n < extrudedIndices[f].Count; n++)
				{
					if(extrudedIndices[f][n].x == val)
					{
						welds[extrudedIndices[f][i].y] = welds[extrudedIndices[f][n].y];
						break;
					}
				}
			}
		}

		localVerts = pb.vertices;

		// Remove smoothing and texture group flags
		foreach(pb_Face f in faces)
		{
			f.SetSmoothingGroup(-1);
			f.textureGroup = -1;
		}

		if(extrudeAsGroup)
		{
			foreach(pb_Face f in faces)
			{
				int[] distinctIndices = f.distinctIndices;

				// Merge in-group face seams
				foreach(int ind in distinctIndices)
				{
					int oldIndex = si.IndexOf(ind);

					for(int n = 0; n < allEdgeIndices.Length; n++)
					{
						for(int i = 0; i < extrudedIndices[n].Count; i++)
						{
							if(oldIndex == extrudedIndices[n][i].x)
							{
								welds[ind] = welds[extrudedIndices[n][i].y];
								break;
							}
						}
					}
				}
			}
		}
		else
		/**
		 * If extruding as separate faces, weld each face to the tops of the bridging faces
		 */
		{
			// Dictionary<int, int> hold = si.ToDictionary();

			for(int i = 0; i < edgeFaces.Length; i++)
			{
				foreach(int n in pb_Face.AllTrianglesDistinct(edgeFaces[i]))
				{
					int old_si_index = lookup[n];
					int match = extrudedIndices[i].FindIndex(x => x.x == old_si_index);

					if(match < 0)
						continue;
					
					int match_tri_index = extrudedIndices[i][match].y;

					if(welds.ContainsKey(match_tri_index))
					{
						welds[n] = welds[match_tri_index];
					}
				}
			}

		}
		
		si = welds.ToSharedIndices();


		pb.SplitUVs(pb_Face.AllTriangles(faces));
		
		/**
		 * Move the inside faces to the top of the extrusion
		 *
		 * This is a separate loop cause the one above this must completely merge all sharedindices prior to 
		 * checking the normal averages
		 *
		 */
		Vector3 norm = Vector3.zero;
		int[] allIndices = pb_Face.AllTrianglesDistinct(faces);
		foreach(pb_Face f in faces)
		{
			if(!extrudeAsGroup)
			{
				norm = pb_Math.Normal( localVerts[f.indices[0]], localVerts[f.indices[1]], localVerts[f.indices[2]]);
			}

			foreach(int ind in f.distinctIndices)
			{
				if(extrudeAsGroup)
					norm = Norm( sharedIndices[lookup[ind]], allIndices, normals );

				localVerts[ind] += norm.normalized * extrudeDistance;
			}
		}

		// Test the winding of the first pulled face, and reverse if it's ccw
		if(pb.GetWindingOrder(faces[0]) == WindingOrder.CounterClockwise)
		{
			foreach(pb_Face face in appendedFaces)
				face.ReverseIndices();
		}

		pb.SetSharedIndices(si);
		pb.SetVertices(localVerts);


		return true;
	}