/** * If any of the faces in @selection are AutoUV and in a texture group, this * augments the texture group buddies to the selection and returns it. */ private pb_Face[] SelectTextureGroups(pb_Object pb, pb_Face[] selection) { List<int> texGroups = selection.Select(x => x.textureGroup).Where(x => x > 0).Distinct().ToList(); pb_Face[] sel = System.Array.FindAll(pb.faces, x => !x.manualUV && texGroups.Contains(x.textureGroup)); return selection.Union(sel).ToArray(); }
/** * If selection contains faces that are part of a texture group, and not all of those group faces are in the selection, * return a pb_Face[] of that entire group so that we can show the user some indication of that groupage. */ private List<pb_Face[]> GetIncompleteTextureGroups(pb_Object pb, pb_Face[] selection) { // get distinct list of all selected texture groups List<int> groups = selection.Select(x => x.textureGroup).Where(x => x > 0).Distinct().ToList(); List<pb_Face[]> incompleteGroups = new List<pb_Face[]>(); // figure out how many for(int i = 0; i < groups.Count; i++) { pb_Face[] whole_group = System.Array.FindAll(pb.faces, x => !x.manualUV && groups[i] == x.textureGroup); int inSelection = System.Array.FindAll(selection, x => x.textureGroup == groups[i]).Length; if(inSelection != whole_group.Length) incompleteGroups.Add(whole_group); } return incompleteGroups; }
/** * 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; }