/** * Get all edges that are on the perimeter of this face group selection. */ public static IEnumerable <pb_Edge> GetPerimeterEdges(pb_Object pb, Dictionary <int, int> sharedIndicesLookup, IEnumerable <pb_Face> faces) { List <pb_Edge> faceEdges = faces.SelectMany(x => x.edges).ToList(); /// actual edges int edgeCount = faceEdges.Count; // translate all face edges to universal edges Dictionary <pb_Edge, List <pb_Edge> > dup = new Dictionary <pb_Edge, List <pb_Edge> >(); List <pb_Edge> list; for (int i = 0; i < edgeCount; i++) { pb_Edge uni = new pb_Edge(sharedIndicesLookup[faceEdges[i].x], sharedIndicesLookup[faceEdges[i].y]); if (dup.TryGetValue(uni, out list)) { list.Add(faceEdges[i]); } else { dup.Add(uni, new List <pb_Edge>() { faceEdges[i] }); } } return(dup.Where(x => x.Value.Count < 2).Select(x => x.Value[0])); }
public static List <pb_Face> GetNeighborFaces(pb_Object pb, pb_Edge edge, Dictionary <int, int> lookup) { List <pb_Face> faces = new List <pb_Face>(); pb_Edge uni = new pb_Edge(lookup[edge.x], lookup[edge.y]); pb_Edge e = new pb_Edge(0, 0); for (int i = 0; i < pb.faces.Length; i++) { pb_Edge[] edges = pb.faces[i].edges; for (int n = 0; n < edges.Length; n++) { e.x = edges[n].x; e.y = edges[n].y; if ((uni.x == lookup[e.x] && uni.y == lookup[e.y]) || (uni.x == lookup[e.y] && uni.y == lookup[e.x])) { faces.Add(pb.faces[i]); break; } } } return(faces); }
/** * \brief Returns all connected faces keeping adjacent faces ordered to correspond to the order they were passed. */ public static List<pb_Face>[][] GetConnectedFacesJagged(pb_Object pb, pb_Face[] selFaces) { int len = selFaces.Length; List<pb_Face>[][] faces = new List<pb_Face>[len][]; for(int j = 0; j < len; j++) { faces[j] = new List<pb_Face>[selFaces[j].edges.Length]; for(int i = 0; i < selFaces[j].edges.Length; i++) faces[j][i] = new List<pb_Face>(); } pb_IntArray[] sharedIndices = pb.sharedIndices; pb_Edge[][] sharedEdges = new pb_Edge[len][]; for(int i = 0; i < len; i++) sharedEdges[i] = pb_Edge.GetUniversalEdges(selFaces[i].edges, sharedIndices); for(int i = 0; i < pb.faces.Length; i++) { pb_Edge[] faceEdges = pb_Edge.GetUniversalEdges(pb.faces[i].edges, sharedIndices); for(int j = 0; j < len; j++) { int ind = faceEdges.ContainsMatch(sharedEdges[j]); if(ind > -1) faces[j][ind].Add(pb.faces[i]); } } return faces; }
/** * Returns an array of faces where each face has at least one non-shared edge. */ public static IEnumerable <pb_Face> GetPerimeterFaces(pb_Object pb, IEnumerable <pb_Face> faces) { Dictionary <int, int> lookup = pb.sharedIndices.ToDictionary(); Dictionary <pb_Edge, List <pb_Face> > sharedEdges = new Dictionary <pb_Edge, List <pb_Face> >(); /** * To be considered a perimeter face, at least one edge must not share * any boundary with another face. */ foreach (pb_Face face in faces) { foreach (pb_Edge e in face.edges) { pb_Edge edge = new pb_Edge(lookup[e.x], lookup[e.y]); if (sharedEdges.ContainsKey(edge)) { sharedEdges[edge].Add(face); } else { sharedEdges.Add(edge, new List <pb_Face>() { face }); } } } return(sharedEdges.Where(x => x.Value.Count < 2).Select(x => x.Value[0]).Distinct()); }
/** * \brief Returns all connected faces. */ public static List <pb_Face> GetConnectedFaces(pb_Object pb, pb_Face[] selFaces) { int len = selFaces.Length; List <pb_Face> faces = new List <pb_Face>(); pb_IntArray[] sharedIndices = pb.sharedIndices; pb_Edge[][] sharedEdges = new pb_Edge[len][]; for (int i = 0; i < len; i++) { sharedEdges[i] = pb_Edge.GetUniversalEdges(selFaces[i].edges, sharedIndices); } for (int i = 0; i < pb.faces.Length; i++) { pb_Edge[] faceEdges = pb_Edge.GetUniversalEdges(pb.faces[i].edges, sharedIndices); for (int j = 0; j < len; j++) { int ind = faceEdges.ContainsMatch(sharedEdges[j]); if (ind > -1) { faces.Add(pb.faces[i]); } } } return(faces); }
/** * Returns a unique array of Edges connected to the passed vertex indices. */ public static pb_Edge[] GetConnectedEdges(pb_Object pb, int[] indices) { Dictionary <int, int> lookup = pb.sharedIndices.ToDictionary(); List <pb_Edge> connectedEdges = new List <pb_Edge>(); HashSet <int> shared = new HashSet <int>(); for (int i = 0; i < indices.Length; i++) { shared.Add(lookup[indices[i]]); } pb_Edge[] edges = pb_Edge.AllEdges(pb.faces); HashSet <pb_Edge> used = new HashSet <pb_Edge>(); pb_Edge uni = new pb_Edge(0, 0); for (int i = 0; i < edges.Length; i++) { pb_Edge key = new pb_Edge(lookup[edges[i].x], lookup[edges[i].y]); if (shared.Contains(key.x) || shared.Contains(key.y) && !used.Contains(uni)) { connectedEdges.Add(edges[i]); used.Add(key); } } return(connectedEdges.ToArray()); }
void ExtrudeEdge() { pb_Face sourceFace = lastExtrudedFace; // fetch a random perimeter edge connected to the last face extruded List <pb_WingedEdge> wings = pb_WingedEdge.GetWingedEdges(pb); IEnumerable <pb_WingedEdge> sourceWings = wings.Where(x => x.face == sourceFace); List <pb_Edge> nonManifoldEdges = sourceWings.Where(x => x.opposite == null).Select(y => y.edge.local).ToList(); int rand = (int)Random.Range(0, nonManifoldEdges.Count); pb_Edge sourceEdge = nonManifoldEdges[rand]; // get the direction this edge should extrude in Vector3 dir = ((pb.vertices[sourceEdge.x] + pb.vertices[sourceEdge.y]) * .5f) - sourceFace.distinctIndices.Average(x => pb.vertices[x]); dir.Normalize(); // this will be populated with the extruded edge pb_Edge[] extrudedEdges; // perform extrusion pb.Extrude(new pb_Edge[] { sourceEdge }, 0f, false, true, out extrudedEdges); // get the last extruded face lastExtrudedFace = pb.faces.Last(); // translate the vertices- pb.TranslateVertices(extrudedEdges[0].ToArray(), dir * distance); // rebuild mesh with new geometry added by extrude pb.ToMesh(); // rebuild mesh normals, textures, collisions, etc pb.Refresh(); }
/** * Iterates through face edges and builds a list using the opposite edge. * @todo Lots of slow stuff in here */ public static pb_Edge[] GetEdgeRing(pb_Object pb, pb_Edge[] edges) { List <pb_Edge> usedEdges = new List <pb_Edge>(); Dictionary <int, int> lookup = pb.sharedIndices.ToDictionary(); foreach (pb_Edge e in edges) { List <pb_Face> origFace; List <pb_Edge> origEdge; // ValidFaceAndEdgeWithEdge will return false if < 1 face and edge combo is found. if (!ValidFaceAndEdgeWithEdge(pb, e, lookup, out origFace, out origEdge)) { continue; } // Only add the initial edge once usedEdges.Add(origEdge[0]); pb_Face opFace; pb_Edge opEdge; bool superBreak = false; for (int i = 0; i < origFace.Count; i++) { pb_Face curFace = origFace[i]; pb_Edge curEdge = origEdge[i]; while (GetOppositeEdge(pb, curFace, curEdge, lookup, out opFace, out opEdge)) { curFace = opFace; curEdge = opEdge; usedEdges.Add(curEdge); if (curFace == null) { break; } if (curFace == origFace[i]) { superBreak = true; break; } } if (superBreak) { break; } } } pb_Edge[] dist = pb_Edge.GetUniversalEdges(usedEdges.ToArray(), lookup); return(pb_Edge.GetLocalEdges_Fast(dist.Distinct().ToArray(), pb.sharedIndices)); }
/** * Get vertices at x,y index with edge. */ public Vector3[] GetEdgeVertices(pb_Edge edge) { return(new Vector3[] { _vertices[edge.x], _vertices[edge.y] }); }
/** * Iterates through face edges and builds a list using the opposite edge. */ public static pb_Edge[] GetEdgeRing(pb_Object pb, pb_Edge[] edges) { List <pb_Edge> usedEdges = new List <pb_Edge>(); foreach (pb_Edge e in edges) { List <pb_Face> origFace; List <pb_Edge> origEdge; if (!ValidFaceAndEdgeWithEdge(pb, e, out origFace, out origEdge)) { continue; } // ValidFaceAndEdgeWithEdge will return false if < 1 face and edge combo is found. // Only add the initial edge once usedEdges.Add(origEdge[0]); pb_Face opFace; pb_Edge opEdge; bool superBreak = false; for (int i = 0; i < origFace.Count; i++) { pb_Face curFace = origFace[i]; pb_Edge curEdge = origEdge[i]; while (GetOppositeEdge(pb, curFace, curEdge, out opFace, out opEdge)) { curFace = opFace; curEdge = opEdge; usedEdges.Add(curEdge); if (curFace == null) { break; } if (curFace == origFace[i]) { superBreak = true; break; } } if (superBreak) { break; } } } return(usedEdges.Distinct().ToArray()); }
public pb_Edge[] GetEdges() { pb_Edge[] edges = new pb_Edge[indices.Length]; for (int i = 0; i < indices.Length; i += 3) { edges[i + 0] = new pb_Edge(indices[i + 0], indices[i + 1]); edges[i + 1] = new pb_Edge(indices[i + 1], indices[i + 2]); edges[i + 2] = new pb_Edge(indices[i + 2], indices[i + 0]); } return(edges); }
/** * Returns all faces connected to the passed edge. */ public static List<pb_Face> GetConnectedFaces(pb_Object pb, pb_Edge edge) { List<pb_Face> faces = new List<pb_Face>(); pb_IntArray[] sharedIndices = pb.sharedIndices; foreach(pb_Face f in pb.faces) { if(f.edges.IndexOf(edge, sharedIndices) > -1) faces.Add(f); } return faces; }
/** * Returns the opposite edge on the neighboring face (if possible - if the edge does not connect to an additional face opposite_face will be null). */ public static bool GetOppositeEdge(pb_Object pb, pb_Face face, pb_Edge edge, Dictionary <int, int> lookup, out pb_Face opposite_face, out pb_Edge opposite_edge) { opposite_face = null; opposite_edge = null; if (face.edges.Length != 4) { return(false); } // Construct a list of all edges starting at vertex edge.y and going around the face. Then grab the middle edge. pb_Edge[] ordered_edges = new pb_Edge[face.edges.Length]; ordered_edges[0] = edge; for (int i = 1; i < face.edges.Length; i++) { foreach (pb_Edge e in face.edges) { if (e.x == ordered_edges[i - 1].y) { ordered_edges[i] = e; break; } } } pb_Edge opEdgeLocal = ordered_edges[face.edges.Length / 2]; List <pb_Face> connectedFaces = pbMeshUtils.GetNeighborFaces(pb, opEdgeLocal, lookup); connectedFaces.Remove(face); if (connectedFaces.Count < 1) { opposite_edge = opEdgeLocal; // sometimes ya still want this edge (planes, for example) return(true); } opposite_face = connectedFaces[0]; for (int i = 0; i < opposite_face.edges.Length; i++) { if (opposite_face.edges[i].Equals(opEdgeLocal, lookup)) { opposite_edge = opposite_face.edges[i]; break; } } return(true); }
/** * Returns all faces connected to the passed edge. */ public static List <pb_Face> GetConnectedFaces(pb_Object pb, pb_Edge edge) { List <pb_Face> faces = new List <pb_Face>(); pb_IntArray[] sharedIndices = pb.sharedIndices; foreach (pb_Face f in pb.faces) { if (f.edges.IndexOf(edge, sharedIndices) > -1) { faces.Add(f); } } return(faces); }
internal static List <pb_Face>[][] GetNeighborFacesJagged(pb_Object pb, pb_Edge[][] selEdges) { int len = selEdges.Length; List <pb_Face>[][] faces = new List <pb_Face> [len][]; for (int j = 0; j < len; j++) { faces[j] = new List <pb_Face> [selEdges[j].Length]; for (int i = 0; i < selEdges[j].Length; i++) { faces[j][i] = new List <pb_Face>(); } } pb_IntArray[] sharedIndices = pb.sharedIndices; pb_Edge[][] sharedEdges = new pb_Edge[len][]; for (int i = 0; i < len; i++) { sharedEdges[i] = pb_Edge.GetUniversalEdges(selEdges[i], sharedIndices).Distinct().ToArray(); } for (int i = 0; i < pb.faces.Length; i++) { pb_Edge[] faceEdges = pb_Edge.GetUniversalEdges(pb.faces[i].edges, sharedIndices).Distinct().ToArray(); for (int j = 0; j < len; j++) { int ind = -1; for (int t = 0; t < sharedEdges[j].Length; t++) { if (faceEdges.Contains(sharedEdges[j][t])) { ind = t; break; } } if (ind > -1) { faces[j][ind].Add(pb.faces[i]); } } } return(faces); }
/** * The SelectedEdges array contains Edges made up of indices that aren't guaranteed to be 'valid' - that is, they * may not belong to the same face. This method extracts an edge and face combo from the face independent edge * selection. * @param faces - Corresponding face to edge list * @param edges - An edge composed of indices that belong to a same face (matching face in faces List). * @returns True if at least one valid edge is found, false if not. */ public static bool ValidFaceAndEdgeWithEdge(pb_Object pb, pb_Edge faceIndependentEdge, Dictionary <int, int> sharedIndices, out List <pb_Face> faces, out List <pb_Edge> edges) { faces = new List <pb_Face>(); edges = new List <pb_Edge>(); foreach (pb_Face f in pb.faces) { int ind = f.edges.IndexOf(faceIndependentEdge, sharedIndices); if (ind > -1) { faces.Add(f); edges.Add(f.edges[ind]); } } return(faces.Count > 0); }
/** * \brief Returns faces that share an edge with any of @c selFcaes. */ public static pb_Face[] GetNeighborFaces(pb_Object pb, Dictionary <int, int> sharedIndicesLookup, pb_Face[] selFaces) { List <pb_Face> perimeterFaces = new List <pb_Face>(); pb_Edge[] perimeterEdges = pbMeshUtils.GetPerimeterEdges(pb, sharedIndicesLookup, selFaces).ToArray(); pb_Edge[] universalEdges = new pb_Edge[perimeterEdges.Length]; for (int i = 0; i < perimeterEdges.Length; i++) { universalEdges[i] = new pb_Edge(sharedIndicesLookup[perimeterEdges[i].x], sharedIndicesLookup[perimeterEdges[i].y]); } pb_Edge edge_u = new pb_Edge(-1, -1); HashSet <pb_Face> skip = new HashSet <pb_Face>(selFaces); foreach (pb_Face face in pb.faces) { if (skip.Contains(face)) { skip.Remove(face); continue; } foreach (pb_Edge edge in face.edges) { edge_u.x = sharedIndicesLookup[edge.x]; edge_u.y = sharedIndicesLookup[edge.y]; if (universalEdges.Contains(edge_u)) { perimeterFaces.Add(face); break; } } } return(perimeterFaces.ToArray()); }
/** * Returns all faces that share an edge with originFace. */ public static List<pb_Face> GetNeighborFaces(pb_Object pb, Dictionary<int, int> lookup, IEnumerable<pb_Face> mask, pb_Face originFace) { List<pb_Face> faces = new List<pb_Face>(); HashSet<pb_Edge> sharedEdges = new HashSet<pb_Edge>(); for(int i = 0; i < originFace.edges.Length; i++) { sharedEdges.Add(new pb_Edge(lookup[originFace.edges[i].x], lookup[originFace.edges[i].y])); } pb_Edge edge_s = new pb_Edge(-1,-1); for(int i = 0; i < pb.faces.Length; i++) { foreach(pb_Edge edge in pb.faces[i].edges) { edge_s.x = lookup[edge.x]; edge_s.y = lookup[edge.y]; bool contains = sharedEdges.Contains(edge_s); if( contains ) { if(mask.Contains(pb.faces[i])) { continue; } faces.Add(pb.faces[i]); break; } } } return faces; }
/** * Returns all faces that share an edge with originFace. */ public static List <pb_Face> GetNeighborFaces(pb_Object pb, Dictionary <int, int> lookup, IEnumerable <pb_Face> mask, pb_Face originFace) { List <pb_Face> faces = new List <pb_Face>(); HashSet <pb_Edge> sharedEdges = new HashSet <pb_Edge>(); for (int i = 0; i < originFace.edges.Length; i++) { sharedEdges.Add(new pb_Edge(lookup[originFace.edges[i].x], lookup[originFace.edges[i].y])); } pb_Edge edge_s = new pb_Edge(-1, -1); for (int i = 0; i < pb.faces.Length; i++) { foreach (pb_Edge edge in pb.faces[i].edges) { edge_s.x = lookup[edge.x]; edge_s.y = lookup[edge.y]; bool contains = sharedEdges.Contains(edge_s); if (contains) { if (mask.Contains(pb.faces[i])) { continue; } faces.Add(pb.faces[i]); break; } } } return(faces); }
private void ExtrudeEdge() { pb_Face sourceFace = lastExtrudedFace; List <pb_WingedEdge> wingedEdges = pb_WingedEdge.GetWingedEdges(pb); IEnumerable <pb_WingedEdge> source = wingedEdges.Where((pb_WingedEdge x) => x.face == sourceFace); List <pb_Edge> list = (from x in source where x.opposite == null select x into y select y.edge.local).ToList(); int index = Random.Range(0, list.Count); pb_Edge pb_Edge = list[index]; Vector3 a = (pb.vertices[pb_Edge.x] + pb.vertices[pb_Edge.y]) * 0.5f - sourceFace.distinctIndices.Average((int x) => pb.vertices[x]); a.Normalize(); pb.Extrude(new pb_Edge[1] { pb_Edge }, 0f, extrudeAsGroup: false, enableManifoldExtrude: true, out pb_Edge[] extrudedEdges); lastExtrudedFace = pb.faces.Last(); pb.SetSelectedEdges(extrudedEdges); pb.TranslateVertices(pb.SelectedTriangles, a * distance); pb.ToMesh(); pb.Refresh(); }
/** * Checks if mouse is over an edge, and if so, returns true setting @edge. */ public static bool EdgeRaycast(Camera cam, Vector2 mousePosition, pb_Object mesh, pb_Edge[] edges, Vector3[] verticesInWorldSpace, out pb_Edge edge) { Vector3 v0, v1; float bestDistance = Mathf.Infinity; float distance = 0f; edge = null; GameObject go = ObjectRaycast(cam, mousePosition, (GameObject[]) Resources.FindObjectsOfTypeAll(typeof(GameObject))); if( go == null || go != mesh.gameObject) { int width = Screen.width; int height = Screen.height; for(int i = 0; i < edges.Length; i++) { v0 = verticesInWorldSpace[edges[i].x]; v1 = verticesInWorldSpace[edges[i].y]; distance = pb_HandleUtility.DistancePoint2DToLine(cam, mousePosition, v0, v1); if ( distance < bestDistance && distance < MAX_EDGE_SELECT_DISTANCE )// && !PointIsOccluded(mesh, (v0+v1)*.5f) ) { Vector3 vs0 = cam.WorldToScreenPoint(v0); // really simple frustum check (will fail on edges that have vertices outside the frustum but is visible) if( vs0.z <= 0 || vs0.x < 0 || vs0.y < 0 || vs0.x > width || vs0.y > height ) continue; Vector3 vs1 = cam.WorldToScreenPoint(v1); if( vs1.z <= 0 || vs1.x < 0 || vs1.y < 0 || vs1.x > width || vs1.y > height ) continue; bestDistance = distance; edge = edges[i]; } } } else { // Test culling List<pb_RaycastHit> hits; Ray ray = cam.ScreenPointToRay(mousePosition);// HandleUtility.GUIPointToWorldRay(mousePosition); if( FaceRaycast(ray, mesh, out hits, Mathf.Infinity, Culling.FrontBack) ) { // Sort from nearest hit to farthest hits.Sort( (x, y) => x.Distance.CompareTo(y.Distance) ); // Find the nearest edge in the hit faces Vector3[] v = mesh.vertices; for(int i = 0; i < hits.Count; i++) { if( pb_HandleUtility.PointIsOccluded(cam, mesh, mesh.transform.TransformPoint(hits[i].Point)) ) continue; foreach(pb_Edge e in mesh.faces[hits[i].FaceIndex].GetEdges()) { float d = pb_Math.DistancePointLineSegment(hits[i].Point, v[e.x], v[e.y]); if(d < bestDistance) { bestDistance = d; edge = e; } } if( Vector3.Dot(ray.direction, mesh.transform.TransformDirection(hits[i].Normal)) < 0f ) break; } if(edge != null && pb_HandleUtility.DistancePoint2DToLine(cam, mousePosition, mesh.transform.TransformPoint(v[edge.x]), mesh.transform.TransformPoint(v[edge.y])) > MAX_EDGE_SELECT_DISTANCE) { edge = null; } // else // { // edge.x = mesh.ToUserIndex(edge.x); // edge.y = mesh.ToUserIndex(edge.y); // } } } return edge != null; }
/** * Iterates through face edges and builds a list using the opposite edge. * @todo Lots of slow stuff in here */ public static pb_Edge[] GetEdgeRing(pb_Object pb, pb_Edge[] edges) { List<pb_Edge> usedEdges = new List<pb_Edge>(); Dictionary<int, int> lookup = pb.sharedIndices.ToDictionary(); foreach(pb_Edge e in edges) { List<pb_Face> origFace; List<pb_Edge> origEdge; // ValidFaceAndEdgeWithEdge will return false if < 1 face and edge combo is found. if( !ValidFaceAndEdgeWithEdge(pb, e, lookup, out origFace, out origEdge) ) continue; // Only add the initial edge once usedEdges.Add(origEdge[0]); pb_Face opFace; pb_Edge opEdge; bool superBreak = false; for(int i = 0; i < origFace.Count; i++) { pb_Face curFace = origFace[i]; pb_Edge curEdge = origEdge[i]; while( GetOppositeEdge(pb, curFace, curEdge, lookup, out opFace, out opEdge) ) { curFace = opFace; curEdge = opEdge; usedEdges.Add(curEdge); if(curFace == null) break; if(curFace == origFace[i]) { superBreak = true; break; } } if(superBreak) break; } } pb_Edge[] dist = pb_Edge.GetUniversalEdges(usedEdges.ToArray(), lookup); return pb_Edge.GetLocalEdges_Fast(dist.Distinct().ToArray(), pb.sharedIndices); }
public void SetSelectedEdges(pb_Edge[] edges) { this.m_selectedFaces = new pb_Face[0]; this.m_SelectedEdges = edges; this.m_selectedTriangles = m_SelectedEdges.ToIntArray(); // this.m_selectedTriangles = sharedIndices.UniqueIndicesWithValues(m_SelectedEdges.ToIntArray()); }
/** * Edge extrusion override */ public static bool Extrude(this pb_Object pb, pb_Edge[] edges, float extrudeDistance, bool extrudeAsGroup, bool enableManifoldExtrude, out pb_Edge[] extrudedEdges) { pb_IntArray[] sharedIndices = pb.sharedIndices; Dictionary<int, int> lookup = sharedIndices.ToDictionary(); List<pb_Edge> validEdges = new List<pb_Edge>(); List<pb_Face> edgeFaces = new List<pb_Face>(); foreach(pb_Edge e in edges) { int faceCount = 0; pb_Face fa = null; foreach(pb_Face f in pb.faces) { if(f.edges.IndexOf(e, lookup) > -1) { fa = f; if(++faceCount > 1) break; } } if(enableManifoldExtrude || faceCount < 2) { validEdges.Add(e); edgeFaces.Add(fa); } } if(validEdges.Count < 1) { extrudedEdges = null; return false; } Vector3[] localVerts = pb.vertices; Vector3[] oNormals = pb.msh.normals; int[] allEdgeIndices = new int[validEdges.Count * 2]; int c = 0; for(int i = 0; i < validEdges.Count; i++) { allEdgeIndices[c++] = validEdges[i].x; allEdgeIndices[c++] = validEdges[i].y; } List<pb_Edge> extrudedIndices = new List<pb_Edge>(); List<pb_Edge> newEdges = new List<pb_Edge>(); // used to set the editor selection to the newly created edges /// build out new faces around validEdges for(int i = 0; i < validEdges.Count; i++) { pb_Edge edge = validEdges[i]; pb_Face face = edgeFaces[i]; // Averages the normals using only vertices that are on the edge Vector3 xnorm = extrudeAsGroup ? Norm( sharedIndices[lookup[edge.x]], allEdgeIndices, oNormals ) : pb_Math.Normal(pb, face); Vector3 ynorm = extrudeAsGroup ? Norm( sharedIndices[lookup[edge.y]], allEdgeIndices, oNormals ) : pb_Math.Normal(pb, face); int x_sharedIndex = lookup[edge.x]; int y_sharedIndex = lookup[edge.y]; pb_Face newFace = pb.AppendFace( new Vector3[4] { localVerts [ edge.x ], localVerts [ edge.y ], localVerts [ edge.x ] + xnorm.normalized * extrudeDistance, localVerts [ edge.y ] + ynorm.normalized * extrudeDistance }, new Color[4] { pb.colors[ edge.x ], pb.colors[ edge.y ], pb.colors[ edge.x ], pb.colors[ edge.y ] }, new Vector2[4], new pb_Face( new int[6] {2, 1, 0, 2, 3, 1 }, face.material, new pb_UV(), 0, -1, -1, false ), new int[4] { x_sharedIndex, y_sharedIndex, -1, -1 }); newEdges.Add(new pb_Edge(newFace.indices[3], newFace.indices[4])); extrudedIndices.Add(new pb_Edge(x_sharedIndex, newFace.indices[3])); extrudedIndices.Add(new pb_Edge(y_sharedIndex, newFace.indices[4])); } sharedIndices = pb.sharedIndices; // merge extruded vertex indices with each other if(extrudeAsGroup) { for(int i = 0; i < extrudedIndices.Count; i++) { int val = extrudedIndices[i].x; for(int n = 0; n < extrudedIndices.Count; n++) { if(n == i) continue; if(extrudedIndices[n].x == val) { pb_IntArrayUtility.MergeSharedIndices(ref sharedIndices, extrudedIndices[n].y, extrudedIndices[i].y); break; } } } } pb.SetSharedIndices(sharedIndices); pb.RebuildFaceCaches(); extrudedEdges = newEdges.ToArray(); return true; }
/** * If in Edge mode, finds the nearest Edge to the mouse */ private void UpdateMouse(Vector3 mousePosition) { if(selection.Length < 1) return; switch(selectionMode) { // default: case SelectMode.Edge: GameObject go = HandleUtility.PickGameObject(mousePosition, false); pb_Edge bestEdge = null; pb_Object bestObj = go == null ? null : go.GetComponent<pb_Object>(); if(bestObj != null && !selection.Contains(bestObj)) { bestObj = null; bestEdge = null; goto SkipMouseCheck; } /** * If mouse isn't over a pb object, it still may be near enough to an edge. */ if(bestObj == null) { // TODO float bestDistance = MAX_EDGE_SELECT_DISTANCE; try { for(int i = 0; i < m_universalEdges.Length; i++) { pb_Edge[] edges = m_universalEdges[i]; for(int j = 0; j < edges.Length; j++) { int x = selection[i].sharedIndices[edges[j].x][0]; int y = selection[i].sharedIndices[edges[j].y][0]; Vector3 world_vert_x = m_verticesInWorldSpace[i][x]; Vector3 world_vert_y = m_verticesInWorldSpace[i][y]; float d = HandleUtility.DistanceToLine(world_vert_x, world_vert_y); if(d < bestDistance) { bestObj = selection[i]; bestEdge = new pb_Edge(x, y); bestDistance = d; } } } } catch {} } else { // Test culling List<pb_RaycastHit> hits; Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition); if(pb_Handle_Utility.MeshRaycast(ray, bestObj, out hits, Mathf.Infinity, Culling.FrontBack)) { Camera cam = SceneView.lastActiveSceneView.camera; // Sort from nearest hit to farthest hits.Sort( (x, y) => x.Distance.CompareTo(y.Distance) ); // Find the nearest edge in the hit faces float bestDistance = Mathf.Infinity; Vector3[] v = bestObj.vertices; for(int i = 0; i < hits.Count; i++) { if( pb_HandleUtility.PointIsOccluded(cam, bestObj, bestObj.transform.TransformPoint(hits[i].Point)) ) continue; foreach(pb_Edge edge in bestObj.faces[hits[i].FaceIndex].edges) { float d = HandleUtility.DistancePointLine(hits[i].Point, v[edge.x], v[edge.y]); if(d < bestDistance) { bestDistance = d; bestEdge = edge; } } if( Vector3.Dot(ray.direction, bestObj.transform.TransformDirection(hits[i].Normal)) < 0f ) break; } if(bestEdge != null && HandleUtility.DistanceToLine(bestObj.transform.TransformPoint(v[bestEdge.x]), bestObj.transform.TransformPoint(v[bestEdge.y])) > MAX_EDGE_SELECT_DISTANCE) bestEdge = null; } } SkipMouseCheck: if(bestEdge != nearestEdge || bestObj != nearestEdgeObject) { nearestEdge = bestEdge; nearestEdgeObject = bestObj; SceneView.RepaintAll(); } break; } }
// public static pb_Edge[] GetEdgeLoop(pb_Object pb, pb_Edge[] edges) // { // List<pb_Edge> ring = new List<pb_Edge>(); // pb_Edge[] orig_uni_edges = pb_Edge.GetUniversalEdges(edges, pb.sharedIndices); // pb_Edge[] all_uni_edges = pb_Edge.GetUniversalEdges(pb_Edge.AllEdges(pb.faces), pb.sharedIndices); // Bugger.Log(orig_uni_edges.ToFormattedString("\n")); // Bugger.Log(all_uni_edges.ToFormattedString("\n")); // foreach(pb_Edge e in orig_uni_edges) // { // ring.Add(e); // int lasty = e.y; // bool foundNeighbor = true; // int n = 0; // while(foundNeighbor && n < 32) // { // foundNeighbor = false; // foreach(pb_Edge ne in all_uni_edges) // { // n++; // if(ne.Equals(e)) continue; // if(ne.x == lasty) // { // Bugger.Log(e + " = " + ne); // lasty = ne.y; // ring.Add(ne); // foundNeighbor = true; // break; // } // } // } // } // pb_Edge[] tri_ring = ring.Distinct().ToArray(); // for(int i = 0; i < tri_ring.Length; i++) // { // tri_ring[i].x = pb.sharedIndices[tri_ring[i].x][0]; // tri_ring[i].y = pb.sharedIndices[tri_ring[i].y][0]; // } // return tri_ring; // } /** * Returns the opposite edge on the neighboring face (if possible - if the edge does not connect to an additional face opposite_face will be null). */ public static bool GetOppositeEdge(pb_Object pb, pb_Face face, pb_Edge edge, out pb_Face opposite_face, out pb_Edge opposite_edge) { opposite_face = null; opposite_edge = null; // Construct a list of all edges starting at vertex edge.y and going around the face. Then grab the middle edge. pb_Edge[] ordered_edges = new pb_Edge[face.edges.Length]; ordered_edges[0] = edge; for(int i = 1; i < face.edges.Length; i++) { foreach(pb_Edge e in face.edges) { if(e.x == ordered_edges[i-1].y) { ordered_edges[i] = e; break; } } } pb_Edge opEdgeLocal = ordered_edges[face.edges.Length/2]; List<pb_Face> connectedFaces = pbMeshUtils.GetConnectedFaces(pb, opEdgeLocal); connectedFaces.Remove(face); if(connectedFaces.Count < 1) { opposite_edge = opEdgeLocal; // sometimes ya still want this edge (planes, for example) return true; } opposite_face = connectedFaces[0]; for(int i = 0; i < opposite_face.edges.Length; i++) { if(opposite_face.edges[i].Equals(opEdgeLocal, pb.sharedIndices)) { opposite_edge = opposite_face.edges[i]; break; } } return true; }
/** * Iterates through face edges and builds a list using the opposite edge. */ public static pb_Edge[] GetEdgeRing(pb_Object pb, pb_Edge[] edges) { List<pb_Edge> usedEdges = new List<pb_Edge>(); foreach(pb_Edge e in edges) { List<pb_Face> origFace; List<pb_Edge> origEdge; if( !ValidFaceAndEdgeWithEdge(pb, e, out origFace, out origEdge) ) continue; // ValidFaceAndEdgeWithEdge will return false if < 1 face and edge combo is found. // Only add the initial edge once usedEdges.Add(origEdge[0]); pb_Face opFace; pb_Edge opEdge; bool superBreak = false; for(int i = 0; i < origFace.Count; i++) { pb_Face curFace = origFace[i]; pb_Edge curEdge = origEdge[i]; while( GetOppositeEdge(pb, curFace, curEdge, out opFace, out opEdge) ) { curFace = opFace; curEdge = opEdge; usedEdges.Add(curEdge); if(curFace == null) break; if(curFace == origFace[i]) { superBreak = true; break; } } if(superBreak) break; } } return usedEdges.Distinct().ToArray(); }
/** * */ 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; }
public static bool Bridge(this pb_Object pb, pb_Edge a, pb_Edge b) { return pb.Bridge(a, b, true); }
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); }
/** * Get vertices at x,y index with edge. */ public Vector3[] GetVertices(pb_Edge edge) { return new Vector3[] { _vertices[edge.x], _vertices[edge.y] }; }
public static bool Bridge(this pb_Object pb, pb_Edge a, pb_Edge b) { return(pb.Bridge(a, b, true)); }
/** * Edge extrusion override */ public static pb_Edge[] Extrude(this pb_Object pb, pb_Edge[] edges, float extrudeDistance, bool enforcePerimiterEdgesOnly) { pb_IntArray[] sharedIndices = pb.sharedIndices; List <pb_Edge> validEdges = new List <pb_Edge>(); List <pb_Face> edgeFaces = new List <pb_Face>(); foreach (pb_Edge e in edges) { int faceCount = 0; pb_Face fa = null; foreach (pb_Face f in pb.faces) { if (f.edges.IndexOf(e, sharedIndices) > -1) { fa = f; if (++faceCount > 1) { break; } } } if (!enforcePerimiterEdgesOnly || faceCount < 2) { validEdges.Add(e); edgeFaces.Add(fa); } } if (validEdges.Count < 1) { return(null); } Vector3[] localVerts = pb.vertices; Vector3[] oNormals = pb.msh.normals; int[] allEdgeIndices = new int[validEdges.Count * 2]; int c = 0; // har har har for (int i = 0; i < validEdges.Count; i++) { allEdgeIndices[c++] = validEdges[i].x; allEdgeIndices[c++] = validEdges[i].y; } List <pb_Edge> extrudedIndices = new List <pb_Edge>(); List <pb_Edge> newEdges = new List <pb_Edge>(); // used to set the editor selection to the newly created edges /// build out new faces around validEdges for (int i = 0; i < validEdges.Count; i++) { pb_Edge edge = validEdges[i]; pb_Face face = edgeFaces[i]; // Averages the normals using only vertices that are on the edge Vector3 xnorm = Norm(edge.x, sharedIndices, allEdgeIndices, oNormals); Vector3 ynorm = Norm(edge.y, sharedIndices, allEdgeIndices, oNormals); int x_sharedIndex = sharedIndices.IndexOf(edge.x); int y_sharedIndex = sharedIndices.IndexOf(edge.y); pb_Face newFace = pb.AppendFace( new Vector3[4] { localVerts [edge.x], localVerts [edge.y], localVerts [edge.x] + xnorm.normalized * extrudeDistance, localVerts [edge.y] + ynorm.normalized * extrudeDistance }, new pb_Face(new int[6] { 2, 1, 0, 2, 3, 1 }, face.material, new pb_UV(), 0, -1, -1, face.colors[0]), new int[4] { x_sharedIndex, y_sharedIndex, -1, -1 }); newEdges.Add(new pb_Edge(newFace.indices[3], newFace.indices[4])); extrudedIndices.Add(new pb_Edge(x_sharedIndex, newFace.indices[3])); extrudedIndices.Add(new pb_Edge(y_sharedIndex, newFace.indices[4])); } sharedIndices = pb.sharedIndices; // merge extruded vertex indices with each other for (int i = 0; i < extrudedIndices.Count; i++) { int val = extrudedIndices[i].x; for (int n = 0; n < extrudedIndices.Count; n++) { if (n == i) { continue; } if (extrudedIndices[n].x == val) { pb_IntArrayUtility.MergeSharedIndices(ref sharedIndices, extrudedIndices[n].y, extrudedIndices[i].y); break; } } } pb.SetSharedIndices(sharedIndices); pb.RebuildFaceCaches(); return(newEdges.ToArray()); }
/** * move the UVs to where the edges passed meet */ static bool AlignEdges(pb_Object pb, pb_Face f1, pb_Face f2, pb_Edge edge1, pb_Edge edge2) { Vector2[] uvs = pb.uv; pb_IntArray[] sharedIndices = pb.sharedIndices; pb_IntArray[] sharedIndicesUV = pb.sharedIndicesUV; /** * Match each edge vertex to the other */ int[] matchX = new int[2] { edge1.x, -1 }; int[] matchY = new int[2] { edge1.y, -1 }; int siIndex = sharedIndices.IndexOf(edge1.x); if (siIndex < 0) { return(false); } if (sharedIndices[siIndex].array.Contains(edge2.x)) { matchX[1] = edge2.x; matchY[1] = edge2.y; } else { matchX[1] = edge2.y; matchY[1] = edge2.x; } // scale face 2 to match the edge size of f1 float dist_e1 = Vector2.Distance(uvs[edge1.x], uvs[edge1.y]); float dist_e2 = Vector2.Distance(uvs[edge2.x], uvs[edge2.y]); float scale = dist_e1 / dist_e2; // doesn't matter what point we scale around because we'll move it in the next step anyways foreach (int i in f2.distinctIndices) { uvs[i] = uvs[i].ScaleAroundPoint(Vector2.zero, Vector2.one * scale); } /** * Figure out where the center of each edge is so that we can move the f2 edge to match f1's origin */ Vector2 f1_center = (uvs[edge1.x] + uvs[edge1.y]) / 2f; Vector2 f2_center = (uvs[edge2.x] + uvs[edge2.y]) / 2f; Vector2 diff = f1_center - f2_center; /** * Move f2 face to where it's matching edge center is on top of f1's center */ foreach (int i in f2.distinctIndices) { uvs[i] += diff; } /** * Now that the edge's centers are matching, rotate f2 to match f1's angle */ Vector2 angle1 = uvs[matchY[0]] - uvs[matchX[0]]; Vector2 angle2 = uvs[matchY[1]] - uvs[matchX[1]]; float angle = Vector2.Angle(angle1, angle2); if (Vector3.Cross(angle1, angle2).z < 0) { angle = 360f - angle; } foreach (int i in f2.distinctIndices) { uvs[i] = pb_Math.RotateAroundPoint(uvs[i], f1_center, angle); } float error = Mathf.Abs(Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]])) + Mathf.Abs(Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]])); // now check that the matched UVs are on top of one another if the error allowance is greater than some small value if (error > .02) { // first try rotating 180 degrees foreach (int i in f2.distinctIndices) { uvs[i] = pb_Math.RotateAroundPoint(uvs[i], f1_center, 180f); } float e2 = Mathf.Abs(Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]])) + Mathf.Abs(Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]])); if (e2 < error) { error = e2; } else { // flip 'em back around foreach (int i in f2.distinctIndices) { uvs[i] = pb_Math.RotateAroundPoint(uvs[i], f1_center, 180f); } } } // If successfully aligned, merge the sharedIndicesUV pbUVOps.SplitUVs(pb, f2.distinctIndices); pb_IntArrayUtility.MergeSharedIndices(ref sharedIndicesUV, matchX); pb_IntArrayUtility.MergeSharedIndices(ref sharedIndicesUV, matchY); pb_IntArray.RemoveEmptyOrNull(ref sharedIndicesUV); pb.SetSharedIndicesUV(sharedIndicesUV); // @todo Update Element Groups here? pb.SetUV(uvs); return(true); }
/** * 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); }
/** * Returns the indices of perimeter vertices in selection. */ public static int[] GetPerimeterVertices(pb_Object pb, int[] indices, pb_Edge[] universal_edges_all) { int len = indices.Length; pb_IntArray[] sharedIndices = pb.sharedIndices; int[] universal = new int[len]; for(int i = 0; i < len; i++) universal[i] = sharedIndices.IndexOf(indices[i]); int[] connections = new int[indices.Length]; for(int i = 0; i < indices.Length - 1; i++) { for(int n = i+1; n < indices.Length; n++) { if(universal_edges_all.Contains(universal[i], universal[n])) { connections[i]++; connections[n]++; } } } int min = pb_Math.Min(connections); List<int> perimeter = new List<int>(); for(int i = 0; i < len; i++) { if(connections[i] <= min) perimeter.Add(i); } return perimeter.Count < len ? perimeter.ToArray() : new int[] {}; }
/** * Returns all faces connected to the passed edge. */ public static List <pb_Face> GetNeighborFaces(pb_Object pb, pb_Edge edge) { return(GetNeighborFaces(pb, edge, pb.sharedIndices.ToDictionary())); }
private void OnSelectionChange() { nearestEdge = null; nearestEdgeObject = null; UpdateSelection(false); HideSelectedWireframe(); }
/** * The SelectedEdges array contains Edges made up of indices that aren't guaranteed to be 'valid' - that is, they * may not belong to the same face. This method extracts an edge and face combo from the face independent edge * selection. * @param faces - Corresponding face to edge list * @param edges - An edge composed of indices that belong to a same face (matching face in faces List). * @returns True if at least one valid edge is found, false if not. */ public static bool ValidFaceAndEdgeWithEdge(pb_Object pb, pb_Edge faceIndependentEdge, out List<pb_Face> faces, out List<pb_Edge> edges) { faces = new List<pb_Face>(); edges = new List<pb_Edge>(); pb_IntArray[] sharedIndices = pb.sharedIndices; foreach(pb_Face f in pb.faces) { int ind = f.edges.IndexOf(faceIndependentEdge, sharedIndices); if(ind > -1) { faces.Add(f); edges.Add(f.edges[ind]); } } return faces.Count > 0; }
private bool EdgeClickCheck(out pb_Object pb) { if(!shiftKey && !ctrlKey) { // don't call ClearFaceSelection b/c that also removes // nearestEdge info foreach(pb_Object p in selection) p.ClearSelection(); } if(nearestEdgeObject != null) { pb = nearestEdgeObject; if(nearestEdge != null && nearestEdge.IsValid()) { pb_Edge edge; if( pb_Edge.ValidateEdge(pb, nearestEdge, out edge) ) nearestEdge = edge; int ind = pb.SelectedEdges.IndexOf(nearestEdge, pb.sharedIndices.ToDictionary()); pbUndo.RecordSelection(pb, "Change Edge Selection"); if( ind > -1 ) pb.SetSelectedEdges(pb.SelectedEdges.RemoveAt(ind)); else pb.SetSelectedEdges(pb.SelectedEdges.Add(nearestEdge)); return true; } return false; } else { if(!shiftKey && !ctrlKey) ClearFaceSelection(); pb = null; return false; } }
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; }
public static bool Bridge(this pb_Object pb, pb_Edge a, pb_Edge b, bool enforcePerimiterEdgesOnly) { pb_IntArray[] sharedIndices = pb.GetSharedIndices(); Dictionary<int, int> lookup = sharedIndices.ToDictionary(); // Check to see if a face already exists if(enforcePerimiterEdgesOnly) { if( pbMeshUtils.GetNeighborFaces(pb, a).Count > 1 || pbMeshUtils.GetNeighborFaces(pb, b).Count > 1 ) { return false; } } foreach(pb_Face face in pb.faces) { if(face.edges.IndexOf(a, lookup) >= 0 && face.edges.IndexOf(b, lookup) >= 0) { Debug.LogWarning("Face already exists between these two edges!"); return false; } } Vector3[] verts = pb.vertices; Vector3[] v; Color[] c; int[] s; pb_UV uvs = new pb_UV(); Material mat = pb_Constant.DefaultMaterial; // Get material and UV stuff from the first edge face foreach(pb_Face face in pb.faces) { if(face.edges.Contains(a)) { uvs = new pb_UV(face.uv); mat = face.material; break; } } // Bridge will form a triangle if( a.Contains(b.x, sharedIndices) || a.Contains(b.y, sharedIndices) ) { v = new Vector3[3]; c = new Color[3]; s = new int[3]; bool axbx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.x) > -1; bool axby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.y) > -1; bool aybx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.x) > -1; bool ayby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.y) > -1; if(axbx) { v[0] = verts[a.x]; c[0] = pb.colors[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; c[1] = pb.colors[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.y]; c[2] = pb.colors[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if(axby) { v[0] = verts[a.x]; c[0] = pb.colors[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; c[1] = pb.colors[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.x]; c[2] = pb.colors[b.x]; s[2] = sharedIndices.IndexOf(b.x); } else if(aybx) { v[0] = verts[a.y]; c[0] = pb.colors[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; c[1] = pb.colors[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.y]; c[2] = pb.colors[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if(ayby) { v[0] = verts[a.y]; c[0] = pb.colors[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; c[1] = pb.colors[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.x]; c[2] = pb.colors[b.x]; s[2] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, c, new Vector2[v.Length], new pb_Face( axbx || axby ? new int[3] {2, 1, 0} : new int[3] {0, 1, 2}, mat, uvs, 0, -1, -1, false ), s); return true; } // Else, bridge will form a quad v = new Vector3[4]; c = new Color[4]; s = new int[4]; // shared indices index to add to v[0] = verts[a.x]; c[0] = pb.colors[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; c[1] = pb.colors[a.y]; s[1] = sharedIndices.IndexOf(a.y); Vector3 nrm = Vector3.Cross( verts[b.x]-verts[a.x], verts[a.y]-verts[a.x] ).normalized; Vector2[] planed = pb_Math.PlanarProject( new Vector3[4] {verts[a.x], verts[a.y], verts[b.x], verts[b.y] }, nrm ); Vector2 ipoint = Vector2.zero; bool interescts = pb_Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint); if(!interescts) { v[2] = verts[b.x]; c[2] = pb.colors[b.x]; s[2] = sharedIndices.IndexOf(b.x); v[3] = verts[b.y]; c[3] = pb.colors[b.y]; s[3] = sharedIndices.IndexOf(b.y); } else { v[2] = verts[b.y]; c[2] = pb.colors[b.y]; s[2] = sharedIndices.IndexOf(b.y); v[3] = verts[b.x]; c[3] = pb.colors[b.x]; s[3] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, c, new Vector2[v.Length], new pb_Face( new int[6] {2, 1, 0, 2, 3, 1 }, mat, uvs, 0, -1, -1, false ), s); return true; }
/** * Clears all `selected` caches associated with each pb_Object in the current selection. The means triangles, faces, and edges. */ public void ClearFaceSelection() { foreach(pb_Object pb in selection) { pb.ClearSelection(); } nearestEdge = null; nearestEdgeObject = null; }
/** * Attempts to insert new edges connecting the center of all passed edges. */ public static bool ConnectEdges(this pb_Object pb, pb_Edge[] edges, out pb_Edge[] newEdges) { // pb_Profiler profiler = new pb_Profiler(); // profiler.BeginSample("Con nectEdges"); int len = edges.Length; List<pb_EdgeConnection> splits = new List<pb_EdgeConnection>(); Dictionary<int, int> lookup = pb.sharedIndices.ToDictionary(); // profiler.BeginSample("Split Edges"); for(int i = 0; i < len; i++) { List<pb_Face> neighbors = pbMeshUtils.GetNeighborFaces(pb, edges[i]); foreach(pb_Face face in neighbors) { if(!splits.Contains((pb_EdgeConnection)face)) { List<pb_Edge> faceEdges = new List<pb_Edge>(); foreach(pb_Edge e in edges) { int localEdgeIndex = face.edges.IndexOf(e, lookup); if(localEdgeIndex > -1) faceEdges.Add(face.edges[localEdgeIndex]); } if(faceEdges.Count > 1) splits.Add(new pb_EdgeConnection(face, faceEdges)); } } } // profiler.EndSample(); Vector3[] vertices = pb.GetVertices( pb_EdgeConnection.AllTriangles(splits).Distinct().ToArray() ); pb_Face[] faces; bool success = ConnectEdges(pb, splits, out faces); // profiler.BeginSample("Find New Edges"); if(success) { /** * Get the newly created Edges so that we can return them. */ List<pb_Edge> nedges = new List<pb_Edge>(); for(int i = 0; i < faces.Length; i++) { for(int n = 0; n < faces[i].edges.Length; n++) { if( pb_Math.ContainsApprox(vertices, pb.vertices[faces[i].edges[n].x], .001f) || pb_Math.ContainsApprox(vertices, pb.vertices[faces[i].edges[n].y], .001f) ) continue; else nedges.Add( faces[i].edges[n] ); } } newEdges = nedges.ToArray(); } else { newEdges = null; } // profiler.EndSample(); // profiler.EndSample(); // Debug.Log(profiler.ToString()); return success; }
/** * 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. */ public static bool GetEdgeLoop(pb_Object pb, pb_Edge[] edges, out pb_Edge[] loop) { pb_IntArray[] sharedIndices = pb.sharedIndices; Dictionary <int, int> lookup = sharedIndices.ToDictionary(); List <pb_Edge> loopEdges = new List <pb_Edge>(); int largestPossibleLoop = pb.vertexCount; int c = 0; bool useY = true; foreach (pb_Edge edge in edges) { if (loopEdges.IndexOf(edge, lookup) > -1) { continue; } // First go in the Y direction. If that doesn't loop, then go in the X direction int cycles = 0; bool nextEdge = false; pb_Edge curEdge = edge; do { if (cycles != 1) { loopEdges.Add(curEdge); } else { cycles++; } // get the index of this triangle in the sharedIndices array // int[] si = sharedIndices[sharedIndices.IndexOf( useY ? curEdge.y : curEdge.x )].array; int[] si = sharedIndices[lookup[useY ? curEdge.y : curEdge.x]].array; // Get all faces connected to this vertex pb_Face[] faces = pbMeshUtils.GetNeighborFaces(pb, si).ToArray(); pb_Face[] edgeAdjacent = System.Array.FindAll(faces, x => x.edges.IndexOf(curEdge, lookup) > -1); pb_Edge[] invalidEdges_universal = pb_Edge.GetUniversalEdges(pb_Edge.AllEdges(edgeAdjacent), sharedIndices).Distinct().ToArray(); // these are faces that do NOT border the current edge pb_Face[] nextFaces = System.Array.FindAll(faces, x => x.edges.IndexOf(curEdge, lookup) < 0); if (nextFaces.Length != 2) { if (cycles < 1) { curEdge = edge; nextEdge = true; useY = false; cycles++; continue; } else { nextEdge = false; break; } } nextEdge = false; bool superBreak = false; for (int i = 0; i < nextFaces.Length; i++) { foreach (pb_Edge e in nextFaces[i].edges) { if (invalidEdges_universal.Contains(pb_Edge.GetUniversalEdge(e, sharedIndices))) { continue; } int xindex = System.Array.IndexOf(si, e.x); int yindex = System.Array.IndexOf(si, e.y); if (xindex > -1 || yindex > -1) { if (e.Equals(edge, lookup)) { // we've completed the loop. exit. superBreak = true; } else { useY = xindex > -1; curEdge = e; superBreak = true; nextEdge = true; } break; } } if (superBreak) { break; } } if (!nextEdge && cycles < 1) { curEdge = edge; nextEdge = true; useY = false; cycles++; } // This is a little arbitrary... if (c++ > largestPossibleLoop) { Debug.LogError("Caught in a loop while searching for a loop! Oh the irony...\n" + loopEdges.Count); nextEdge = false; } }while(nextEdge); } loop = loopEdges.Distinct().ToArray(); return(loopEdges.Count > 0); }
public static bool Bridge(this pb_Object pb, pb_Edge a, pb_Edge b, bool enforcePerimiterEdgesOnly) { pb_IntArray[] sharedIndices = pb.GetSharedIndices(); Dictionary <int, int> lookup = sharedIndices.ToDictionary(); // Check to see if a face already exists if (enforcePerimiterEdgesOnly) { if (pbMeshUtils.GetNeighborFaces(pb, a).Count > 1 || pbMeshUtils.GetNeighborFaces(pb, b).Count > 1) { return(false); } } foreach (pb_Face face in pb.faces) { if (face.edges.IndexOf(a, lookup) >= 0 && face.edges.IndexOf(b, lookup) >= 0) { Debug.LogWarning("Face already exists between these two edges!"); return(false); } } Vector3[] verts = pb.vertices; Vector3[] v; Color[] c; int[] s; pb_UV uvs = new pb_UV(); Material mat = pb_Constant.DefaultMaterial; // Get material and UV stuff from the first edge face foreach (pb_Face face in pb.faces) { if (face.edges.Contains(a)) { uvs = new pb_UV(face.uv); mat = face.material; break; } } // Bridge will form a triangle if (a.Contains(b.x, sharedIndices) || a.Contains(b.y, sharedIndices)) { v = new Vector3[3]; c = new Color[3]; s = new int[3]; bool axbx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.x) > -1; bool axby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.y) > -1; bool aybx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.x) > -1; bool ayby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.y) > -1; if (axbx) { v[0] = verts[a.x]; c[0] = pb.colors[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; c[1] = pb.colors[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.y]; c[2] = pb.colors[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if (axby) { v[0] = verts[a.x]; c[0] = pb.colors[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; c[1] = pb.colors[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.x]; c[2] = pb.colors[b.x]; s[2] = sharedIndices.IndexOf(b.x); } else if (aybx) { v[0] = verts[a.y]; c[0] = pb.colors[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; c[1] = pb.colors[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.y]; c[2] = pb.colors[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if (ayby) { v[0] = verts[a.y]; c[0] = pb.colors[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; c[1] = pb.colors[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.x]; c[2] = pb.colors[b.x]; s[2] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, c, new Vector2[v.Length], new pb_Face(axbx || axby ? new int[3] { 2, 1, 0 } : new int[3] { 0, 1, 2 }, mat, uvs, 0, -1, -1, false), s); return(true); } // Else, bridge will form a quad v = new Vector3[4]; c = new Color[4]; s = new int[4]; // shared indices index to add to v[0] = verts[a.x]; c[0] = pb.colors[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; c[1] = pb.colors[a.y]; s[1] = sharedIndices.IndexOf(a.y); Vector3 nrm = Vector3.Cross(verts[b.x] - verts[a.x], verts[a.y] - verts[a.x]).normalized; Vector2[] planed = pb_Math.PlanarProject(new Vector3[4] { verts[a.x], verts[a.y], verts[b.x], verts[b.y] }, nrm); Vector2 ipoint = Vector2.zero; bool interescts = pb_Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint); if (!interescts) { v[2] = verts[b.x]; c[2] = pb.colors[b.x]; s[2] = sharedIndices.IndexOf(b.x); v[3] = verts[b.y]; c[3] = pb.colors[b.y]; s[3] = sharedIndices.IndexOf(b.y); } else { v[2] = verts[b.y]; c[2] = pb.colors[b.y]; s[2] = sharedIndices.IndexOf(b.y); v[3] = verts[b.x]; c[3] = pb.colors[b.x]; s[3] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, c, new Vector2[v.Length], new pb_Face(new int[6] { 2, 1, 0, 2, 3, 1 }, mat, uvs, 0, -1, -1, false), s); return(true); }
/** * 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. */ public static bool GetEdgeLoop(pb_Object pb, pb_Edge[] edges, out pb_Edge[] loop) { pb_IntArray[] sharedIndices = pb.sharedIndices; Dictionary<int, int> lookup = sharedIndices.ToDictionary(); List<pb_Edge> loopEdges = new List<pb_Edge>(); int largestPossibleLoop = pb.vertexCount; int c = 0; bool useY = true; foreach(pb_Edge edge in edges) { if( loopEdges.IndexOf(edge, lookup) > -1 ) continue; // First go in the Y direction. If that doesn't loop, then go in the X direction int cycles = 0; bool nextEdge = false; pb_Edge curEdge = edge; do { if(cycles != 1) loopEdges.Add(curEdge); else cycles++; // get the index of this triangle in the sharedIndices array // int[] si = sharedIndices[sharedIndices.IndexOf( useY ? curEdge.y : curEdge.x )].array; int[] si = sharedIndices[lookup[useY ? curEdge.y : curEdge.x]].array; // Get all faces connected to this vertex pb_Face[] faces = pbMeshUtils.GetNeighborFaces(pb, si).ToArray(); pb_Face[] edgeAdjacent = System.Array.FindAll(faces, x => x.edges.IndexOf(curEdge, lookup) > -1); pb_Edge[] invalidEdges_universal = pb_Edge.GetUniversalEdges( pb_Edge.AllEdges(edgeAdjacent), sharedIndices ).Distinct().ToArray(); // these are faces that do NOT border the current edge pb_Face[] nextFaces = System.Array.FindAll(faces, x => x.edges.IndexOf(curEdge, lookup) < 0); if(nextFaces.Length != 2) { if(cycles < 1) { curEdge = edge; nextEdge = true; useY = false; cycles++; continue; } else { nextEdge = false; break; } } nextEdge = false; bool superBreak = false; for(int i = 0; i < nextFaces.Length; i++) { foreach(pb_Edge e in nextFaces[i].edges) { if( invalidEdges_universal.Contains(pb_Edge.GetUniversalEdge(e, sharedIndices)) ) continue; int xindex = System.Array.IndexOf(si, e.x); int yindex = System.Array.IndexOf(si, e.y); if( xindex > -1 || yindex > -1 ) { if( e.Equals(edge, lookup) ) { // we've completed the loop. exit. superBreak = true; } else { useY = xindex > -1; curEdge = e; superBreak = true; nextEdge = true; } break; } } if(superBreak) break; } if(!nextEdge && cycles < 1) { curEdge = edge; nextEdge = true; useY = false; cycles++; } // This is a little arbitrary... if(c++ > largestPossibleLoop) { Debug.LogError("Caught in a loop while searching for a loop! Oh the irony...\n" + loopEdges.Count); nextEdge = false; } } while(nextEdge); } loop = loopEdges.Distinct().ToArray(); return loopEdges.Count > 0; }
private static bool ConnectEdges(pb_Object pb, pb_Edge[] edgesToConnect) { int len = edgesToConnect.Length; List<EdgeConnection> splits = new List<EdgeConnection>(); for(int i = 0; i < len; i++) { foreach(pb_Face face in pbMeshUtils.GetConnectedFaces(pb, edgesToConnect[i])) { if(!splits.Contains((EdgeConnection)face)) { List<pb_Edge> faceEdges = new List<pb_Edge>(); foreach(pb_Edge e in edgesToConnect) { int localEdgeIndex = face.edges.IndexOf(e, pb.sharedIndices); if(localEdgeIndex > -1) faceEdges.Add(face.edges[localEdgeIndex]); } if(faceEdges.Count > 1) splits.Add(new EdgeConnection(face, faceEdges)); } } } pb_Face[] faces; if(pb.ConnectEdges(splits, out faces)) { pb.SetSelectedFaces(faces); pb.GenerateUV2(true); pb.Refresh(); return true; } return false; }
public static bool Bridge(this pb_Object pb, pb_Edge a, pb_Edge b, bool enforcePerimiterEdgesOnly) { pb_IntArray[] sharedIndices = pb.GetSharedIndices(); // Check to see if a face already exists if (enforcePerimiterEdgesOnly) { if (pbMeshUtils.GetConnectedFaces(pb, a).Count > 1 || pbMeshUtils.GetConnectedFaces(pb, b).Count > 1) { Debug.LogWarning("Both edges are not on perimeter! You may turn off this Bridging restriction in Preferences/ProBuilder/Bridge Perimiter Edges Only"); return(false); } } else { foreach (pb_Face face in pb.faces) { if (face.edges.IndexOf(a, sharedIndices) >= 0 && face.edges.IndexOf(b, sharedIndices) >= 0) { Debug.LogWarning("Face already exists between these two edges!"); return(false); } } } Vector3[] verts = pb.vertices; Vector3[] v; int[] s; pb_UV uvs = new pb_UV(); Color32 color = (Color32)Color.white; Material mat = pb_Constant.DefaultMaterial; // Get material and UV stuff from the first edge face foreach (pb_Face face in pb.faces) { if (face.edges.Contains(a)) { uvs = new pb_UV(face.uv); mat = face.material; color = face.colors[0]; break; } } // Bridge will form a triangle if (a.Contains(b.x, sharedIndices) || a.Contains(b.y, sharedIndices)) { v = new Vector3[3]; s = new int[3]; bool axbx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.x) > -1; bool axby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.y) > -1; bool aybx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.x) > -1; bool ayby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.y) > -1; if (axbx) { v[0] = verts[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if (axby) { v[0] = verts[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.x]; s[2] = sharedIndices.IndexOf(b.x); } else if (aybx) { v[0] = verts[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if (ayby) { v[0] = verts[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.x]; s[2] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, new pb_Face(axbx || axby ? new int[3] { 2, 1, 0 } : new int[3] { 0, 1, 2 }, mat, uvs, 0, -1, -1, color), s); pb.RebuildFaceCaches(); pb.Refresh(); return(true); } // Else, bridge will form a quad v = new Vector3[4]; s = new int[4]; // shared indices index to add to v[0] = verts[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; s[1] = sharedIndices.IndexOf(a.y); Vector3 nrm = Vector3.Cross(verts[b.x] - verts[a.x], verts[a.y] - verts[a.x]).normalized; Vector2[] planed = pb_Math.VerticesTo2DPoints(new Vector3[4] { verts[a.x], verts[a.y], verts[b.x], verts[b.y] }, nrm); Vector2 ipoint = Vector2.zero; bool interescts = pb_Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint); if (!interescts) { v[2] = verts[b.x]; s[2] = sharedIndices.IndexOf(b.x); v[3] = verts[b.y]; s[3] = sharedIndices.IndexOf(b.y); } else { v[2] = verts[b.y]; s[2] = sharedIndices.IndexOf(b.y); v[3] = verts[b.x]; s[3] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, new pb_Face(new int[6] { 2, 1, 0, 2, 3, 1 }, mat, uvs, 0, -1, -1, color), s); pb.RebuildFaceCaches(); return(true); }
public pb_Edge[] GetEdges() { pb_Edge[] edges = new pb_Edge[indices.Length]; for(int i = 0; i < indices.Length; i+=3) { edges[i+0] = new pb_Edge(indices[i+0],indices[i+1]); edges[i+1] = new pb_Edge(indices[i+1],indices[i+2]); edges[i+2] = new pb_Edge(indices[i+2],indices[i+0]); } return edges; }
public pb_SerializableEdge(pb_Edge v) { this.x = v.x; this.y = v.y; }
public static void Extrude(this pb_Object pb, pb_Face[] faces, float extrudeDistance) { if (faces == null || faces.Length < 1) { return; } pb_IntArray[] sharedIndices = pb.GetSharedIndices(); Vector3[] localVerts = pb.vertices; Vector3[] oNormals = pb.msh.normals; pb_Edge[] perimeterEdges = pb.GetPerimeterEdges(faces); if (perimeterEdges == null || perimeterEdges.Length < 3) { Debug.LogWarning("No perimeter edges found. Try deselecting and reselecting this object and trying again."); return; } pb_Face[] edgeFaces = new pb_Face[perimeterEdges.Length]; // can't assume faces and perimiter edges will be 1:1 - so calculate perimeters then extrace face information int[] allEdgeIndices = new int[perimeterEdges.Length * 2]; int c = 0; for (int i = 0; i < perimeterEdges.Length; i++) { // wtf does this do edgeFaces[i] = faces[0]; foreach (pb_Face face in faces) { if (face.edges.Contains(perimeterEdges[i])) { edgeFaces[i] = face; } } allEdgeIndices[c++] = perimeterEdges[i].x; allEdgeIndices[c++] = perimeterEdges[i].y; } List <pb_Edge> extrudedIndices = new List <pb_Edge>(); /// build out new faces around edges for (int i = 0; i < perimeterEdges.Length; i++) { pb_Edge edge = perimeterEdges[i]; pb_Face face = edgeFaces[i]; // 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 (extrudeDistance > Mathf.Epsilon) { xnorm = Norm(edge.x, sharedIndices, allEdgeIndices, oNormals); ynorm = Norm(edge.y, sharedIndices, allEdgeIndices, oNormals); } int x_sharedIndex = sharedIndices.IndexOf(edge.x); int y_sharedIndex = sharedIndices.IndexOf(edge.y); pb_Face newFace = pb.AppendFace( new Vector3[4] { localVerts [edge.x], localVerts [edge.y], localVerts [edge.x] + xnorm.normalized * extrudeDistance, localVerts [edge.y] + ynorm.normalized * extrudeDistance }, 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 face.colors[0]), // colors new int[4] { x_sharedIndex, y_sharedIndex, -1, -1 }); extrudedIndices.Add(new pb_Edge(x_sharedIndex, newFace.indices[2])); extrudedIndices.Add(new pb_Edge(y_sharedIndex, newFace.indices[4])); } // merge extruded vertex indices with each other pb_IntArray[] si = pb.sharedIndices; // leave the sharedIndices copy alone since we need the un-altered version later for (int i = 0; i < extrudedIndices.Count; i++) { int val = extrudedIndices[i].x; for (int n = 0; n < extrudedIndices.Count; n++) { if (n == i) { continue; } if (extrudedIndices[n].x == val) { pb_IntArrayUtility.MergeSharedIndices(ref si, extrudedIndices[n].y, extrudedIndices[i].y); break; } } } // Move extruded faces to top localVerts = pb.vertices; Dictionary <int, int> remappedTexGroups = new Dictionary <int, int>(); foreach (pb_Face f in faces) { // Remap texture groups if (f.textureGroup > 0) { if (remappedTexGroups.ContainsKey(f.textureGroup)) { f.textureGroup = remappedTexGroups[f.textureGroup]; } else { int newTexGroup = pb.UnusedTextureGroup(); remappedTexGroups.Add(f.textureGroup, newTexGroup); f.textureGroup = newTexGroup; } } int[] distinctIndices = f.distinctIndices; foreach (int ind in distinctIndices) { int oldIndex = si.IndexOf(ind); for (int i = 0; i < extrudedIndices.Count; i++) { if (oldIndex == extrudedIndices[i].x) { pb_IntArrayUtility.MergeSharedIndices(ref si, extrudedIndices[i].y, ind); break; } } } } // this is a separate loop cause the one above this must completely merge all sharedindices prior to // checking the normal averages foreach (pb_Face f in faces) { foreach (int ind in f.distinctIndices) { Vector3 norm = Norm(ind, si, allEdgeIndices, oNormals); localVerts[ind] += norm.normalized * extrudeDistance; } } pb.SetSharedIndices(si); pb.SetVertices(localVerts); pb.RebuildFaceCaches(); }
public void SetSelectedEdges(pb_Edge[] edges) { this.m_selectedFaces = new int[0]; int len = edges.Length; this.m_SelectedEdges = new pb_Edge[len]; for(int i = 0; i < len; i++) this.m_SelectedEdges[i] = new pb_Edge(edges[i]); this.m_selectedTriangles = m_SelectedEdges.AllTriangles(); }
/** * Invert the current selection. */ public static void MenuInvertSelection(pb_Object[] selection) { pbUndo.RecordSelection(selection, "Invert Selection"); switch( editor != null ? editor.selectionMode : (SelectMode)0 ) { case SelectMode.Vertex: foreach(pb_Object pb in selection) { pb_IntArray[] sharedIndices = pb.sharedIndices; List<int> selSharedIndices = new List<int>(); foreach(int i in pb.SelectedTriangles) selSharedIndices.Add( sharedIndices.IndexOf(i) ); List<int> inverse = new List<int>(); for(int i = 0; i < sharedIndices.Length; i++) { if(!selSharedIndices.Contains(i)) inverse.Add(sharedIndices[i][0]); } pb.SetSelectedTriangles(inverse.ToArray()); } break; case SelectMode.Face: foreach(pb_Object pb in selection) { List<pb_Face> inverse = new List<pb_Face>(); for(int i = 0; i < pb.faces.Length; i++) if( System.Array.IndexOf(pb.SelectedFaceIndices, i) < 0 ) inverse.Add(pb.faces[i]); pb.SetSelectedFaces(inverse.ToArray()); } break; case SelectMode.Edge: if(!editor) break; for(int i = 0; i < selection.Length; i++) { pb_Edge[] universal_selected_edges = pb_Edge.GetUniversalEdges(selection[i].SelectedEdges, selection[i].sharedIndices).Distinct().ToArray(); pb_Edge[] inverse_universal = System.Array.FindAll(editor.SelectedUniversalEdges[i], x => !universal_selected_edges.Contains(x)); pb_Edge[] inverse = new pb_Edge[inverse_universal.Length]; for(int n = 0; n < inverse_universal.Length; n++) inverse[n] = new pb_Edge( selection[i].sharedIndices[inverse_universal[n].x][0], selection[i].sharedIndices[inverse_universal[n].y][0] ); selection[i].SetSelectedEdges(inverse); } break; } if(editor) editor.UpdateSelection(); pb_Editor_Utility.ShowNotification("Invert Selection"); SceneView.RepaintAll(); }
/** * Edge extrusion override */ public static pb_Edge[] Extrude(this pb_Object pb, pb_Edge[] edges, float extrudeDistance, bool enforcePerimiterEdgesOnly) { pb_IntArray[] sharedIndices = pb.sharedIndices; List<pb_Edge> validEdges = new List<pb_Edge>(); List<pb_Face> edgeFaces = new List<pb_Face>(); foreach(pb_Edge e in edges) { int faceCount = 0; pb_Face fa = null; foreach(pb_Face f in pb.faces) { if(f.edges.IndexOf(e, sharedIndices) > -1) { fa = f; if(++faceCount > 1) break; } } if(!enforcePerimiterEdgesOnly || faceCount < 2) { validEdges.Add(e); edgeFaces.Add(fa); } } if(validEdges.Count < 1) return null; Vector3[] localVerts = pb.vertices; Vector3[] oNormals = pb.msh.normals; int[] allEdgeIndices = new int[validEdges.Count * 2]; int c = 0; // har har har for(int i = 0; i < validEdges.Count; i++) { allEdgeIndices[c++] = validEdges[i].x; allEdgeIndices[c++] = validEdges[i].y; } List<pb_Edge> extrudedIndices = new List<pb_Edge>(); List<pb_Edge> newEdges = new List<pb_Edge>(); // used to set the editor selection to the newly created edges /// build out new faces around validEdges for(int i = 0; i < validEdges.Count; i++) { pb_Edge edge = validEdges[i]; pb_Face face = edgeFaces[i]; // Averages the normals using only vertices that are on the edge Vector3 xnorm = Norm( edge.x, sharedIndices, allEdgeIndices, oNormals ); Vector3 ynorm = Norm( edge.y, sharedIndices, allEdgeIndices, oNormals ); int x_sharedIndex = sharedIndices.IndexOf(edge.x); int y_sharedIndex = sharedIndices.IndexOf(edge.y); pb_Face newFace = pb.AppendFace( new Vector3[4] { localVerts [ edge.x ], localVerts [ edge.y ], localVerts [ edge.x ] + xnorm.normalized * extrudeDistance, localVerts [ edge.y ] + ynorm.normalized * extrudeDistance }, new pb_Face( new int[6] {2, 1, 0, 2, 3, 1 }, face.material, new pb_UV(), 0, -1, -1, face.colors[0] ), new int[4] { x_sharedIndex, y_sharedIndex, -1, -1 }); newEdges.Add(new pb_Edge(newFace.indices[3], newFace.indices[4])); extrudedIndices.Add(new pb_Edge(x_sharedIndex, newFace.indices[3])); extrudedIndices.Add(new pb_Edge(y_sharedIndex, newFace.indices[4])); } sharedIndices = pb.sharedIndices; // merge extruded vertex indices with each other for(int i = 0; i < extrudedIndices.Count; i++) { int val = extrudedIndices[i].x; for(int n = 0; n < extrudedIndices.Count; n++) { if(n == i) continue; if(extrudedIndices[n].x == val) { pb_IntArrayUtility.MergeSharedIndices(ref sharedIndices, extrudedIndices[n].y, extrudedIndices[i].y); break; } } } pb.SetSharedIndices(sharedIndices); pb.RebuildFaceCaches(); return newEdges.ToArray(); }
/** * move the UVs to where the edges passed meet */ static bool AlignEdges(pb_Object pb, pb_Face f1, pb_Face f2, pb_Edge edge1, pb_Edge edge2) { Vector2[] uvs = pb.uv; pb_IntArray[] sharedIndices = pb.sharedIndices; pb_IntArray[] sharedIndicesUV = pb.sharedIndicesUV; /** * Match each edge vertex to the other */ int[] matchX = new int[2] { edge1.x, -1 }; int[] matchY = new int[2] { edge1.y, -1 }; int siIndex = sharedIndices.IndexOf(edge1.x); if(siIndex < 0) return false; if(sharedIndices[siIndex].array.Contains(edge2.x)) { matchX[1] = edge2.x; matchY[1] = edge2.y; } else { matchX[1] = edge2.y; matchY[1] = edge2.x; } // scale face 2 to match the edge size of f1 float dist_e1 = Vector2.Distance(uvs[edge1.x], uvs[edge1.y]); float dist_e2 = Vector2.Distance(uvs[edge2.x], uvs[edge2.y]); float scale = dist_e1/dist_e2; // doesn't matter what point we scale around because we'll move it in the next step anyways foreach(int i in f2.distinctIndices) uvs[i] = uvs[i].ScaleAroundPoint(Vector2.zero, Vector2.one * scale); /** * Figure out where the center of each edge is so that we can move the f2 edge to match f1's origin */ Vector2 f1_center = (uvs[edge1.x] + uvs[edge1.y]) / 2f; Vector2 f2_center = (uvs[edge2.x] + uvs[edge2.y]) / 2f; Vector2 diff = f1_center - f2_center; /** * Move f2 face to where it's matching edge center is on top of f1's center */ foreach(int i in f2.distinctIndices) uvs[i] += diff; /** * Now that the edge's centers are matching, rotate f2 to match f1's angle */ Vector2 angle1 = uvs[matchY[0]] - uvs[matchX[0]]; Vector2 angle2 = uvs[matchY[1]] - uvs[matchX[1]]; float angle = Vector2.Angle(angle1, angle2); if(Vector3.Cross(angle1, angle2).z < 0) angle = 360f - angle; foreach(int i in f2.distinctIndices) uvs[i] = pb_Math.RotateAroundPoint(uvs[i], f1_center, angle); float error = Mathf.Abs( Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]]) ) + Mathf.Abs( Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]]) ); // now check that the matched UVs are on top of one another if the error allowance is greater than some small value if(error > .02) { // first try rotating 180 degrees foreach(int i in f2.distinctIndices) uvs[i] = pb_Math.RotateAroundPoint(uvs[i], f1_center, 180f); float e2 = Mathf.Abs( Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]]) ) + Mathf.Abs( Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]]) ); if(e2 < error) error = e2; else { // flip 'em back around foreach(int i in f2.distinctIndices) uvs[i] = pb_Math.RotateAroundPoint(uvs[i], f1_center, 180f); } } // If successfully aligned, merge the sharedIndicesUV pbUVOps.SplitUVs(pb, f2.distinctIndices); pb_IntArrayUtility.MergeSharedIndices(ref sharedIndicesUV, matchX); pb_IntArrayUtility.MergeSharedIndices(ref sharedIndicesUV, matchY); pb_IntArray.RemoveEmptyOrNull(ref sharedIndicesUV); pb.SetSharedIndicesUV(sharedIndicesUV); // @todo Update Element Groups here? pb.SetUV(uvs); return true; }
public static bool Bridge(this pb_Object pb, pb_Edge a, pb_Edge b, bool enforcePerimiterEdgesOnly) { pb_IntArray[] sharedIndices = pb.GetSharedIndices(); // Check to see if a face already exists if(enforcePerimiterEdgesOnly) { if( pbMeshUtils.GetConnectedFaces(pb, a).Count > 1 || pbMeshUtils.GetConnectedFaces(pb, b).Count > 1 ) { Debug.LogWarning("Both edges are not on perimeter! You may turn off this Bridging restriction in Preferences/ProBuilder/Bridge Perimiter Edges Only"); return false; } } else { foreach(pb_Face face in pb.faces) { if(face.edges.IndexOf(a, sharedIndices) >= 0 && face.edges.IndexOf(b, sharedIndices) >= 0) { Debug.LogWarning("Face already exists between these two edges!"); return false; } } } Vector3[] verts = pb.vertices; Vector3[] v; int[] s; pb_UV uvs = new pb_UV(); Color32 color = (Color32)Color.white; Material mat = pb_Constant.DefaultMaterial; // Get material and UV stuff from the first edge face foreach(pb_Face face in pb.faces) { if(face.edges.Contains(a)) { uvs = new pb_UV(face.uv); mat = face.material; color = face.colors[0]; break; } } // Bridge will form a triangle if( a.Contains(b.x, sharedIndices) || a.Contains(b.y, sharedIndices) ) { v = new Vector3[3]; s = new int[3]; bool axbx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.x) > -1; bool axby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.x)], b.y) > -1; bool aybx = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.x) > -1; bool ayby = System.Array.IndexOf(sharedIndices[sharedIndices.IndexOf(a.y)], b.y) > -1; if(axbx) { v[0] = verts[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if(axby) { v[0] = verts[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; s[1] = sharedIndices.IndexOf(a.y); v[2] = verts[b.x]; s[2] = sharedIndices.IndexOf(b.x); } else if(aybx) { v[0] = verts[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.y]; s[2] = sharedIndices.IndexOf(b.y); } else if(ayby) { v[0] = verts[a.y]; s[0] = sharedIndices.IndexOf(a.y); v[1] = verts[a.x]; s[1] = sharedIndices.IndexOf(a.x); v[2] = verts[b.x]; s[2] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, new pb_Face( axbx || axby ? new int[3] {2, 1, 0} : new int[3] {0, 1, 2}, mat, uvs, 0, -1, -1, color ), s); pb.RebuildFaceCaches(); pb.Refresh(); return true; } // Else, bridge will form a quad v = new Vector3[4]; s = new int[4]; // shared indices index to add to v[0] = verts[a.x]; s[0] = sharedIndices.IndexOf(a.x); v[1] = verts[a.y]; s[1] = sharedIndices.IndexOf(a.y); Vector3 nrm = Vector3.Cross( verts[b.x]-verts[a.x], verts[a.y]-verts[a.x] ).normalized; Vector2[] planed = pb_Math.VerticesTo2DPoints( new Vector3[4] {verts[a.x], verts[a.y], verts[b.x], verts[b.y] }, nrm ); Vector2 ipoint = Vector2.zero; bool interescts = pb_Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint); if(!interescts) { v[2] = verts[b.x]; s[2] = sharedIndices.IndexOf(b.x); v[3] = verts[b.y]; s[3] = sharedIndices.IndexOf(b.y); } else { v[2] = verts[b.y]; s[2] = sharedIndices.IndexOf(b.y); v[3] = verts[b.x]; s[3] = sharedIndices.IndexOf(b.x); } pb.AppendFace( v, new pb_Face( new int[6] {2, 1, 0, 2, 3, 1 }, mat, uvs, 0, -1, -1, color ), s); pb.RebuildFaceCaches(); return true; }
/** * */ 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); }