/** * Given a face and a point, this will add a vertex to the pb_Object and retriangulate the face. */ public static bool AppendVerticesToFace(this pb_Object pb, pb_Face face, List <Vector3> points, out pb_Face newFace) { if (!face.isValid()) { newFace = face; return(false); } // First order of business - project face to 2d int[] distinctIndices = face.distinctIndices; Vector3[] verts = pb.GetVertices(distinctIndices); // Get the face normal before modifying the vertex array Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector3 projAxis = pb_Math.GetProjectionAxis(nrm).ToVector3(); // Add the new point Vector3[] t_verts = new Vector3[verts.Length + points.Count]; System.Array.Copy(verts, 0, t_verts, 0, verts.Length); System.Array.Copy(points.ToArray(), 0, t_verts, verts.Length, points.Count); verts = t_verts; // Project List <Vector2> plane = new List <Vector2>(pb_Math.VerticesTo2DPoints(verts, projAxis)); // Save the sharedIndices index for each distinct vertex pb_IntArray[] sharedIndices = pb.sharedIndices; int[] sharedIndex = new int[distinctIndices.Length + points.Count]; for (int i = 0; i < distinctIndices.Length; i++) { sharedIndex[i] = sharedIndices.IndexOf(distinctIndices[i]); } for (int i = distinctIndices.Length; i < distinctIndices.Length + points.Count; i++) { sharedIndex[i] = -1; // add the new vertex to it's own sharedIndex } // Triangulate the face with the new point appended int[] tris = Delauney.Triangulate(plane).ToIntArray(); // Check to make sure the triangulated face is facing the same direction, and flip if not Vector3 del = Vector3.Cross(verts[tris[2]] - verts[tris[0]], verts[tris[1]] - verts[tris[0]]).normalized; if (Vector3.Dot(nrm, del) > 0) { System.Array.Reverse(tris); } // Compose new face newFace = pb.AppendFace(verts, new pb_Face(tris, face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, -1, face.color), sharedIndex); // And delete the old pb.DeleteFace(face); return(true); }
public static bool HiddenFace(pb_Object pb, pb_Face q, float dist) { // Grab the face normal Vector3 dir = pb_Math.Normal(pb.VerticesInWorldSpace(q.indices)); // If casting from the center of the plane hits, chekc the rest of the points for collisions Vector3 orig = pb.transform.TransformPoint(pb_Math.Average(pb.GetVertices(q))); bool hidden = true; Transform hitObj = RaycastFaceCheck(orig, dir, dist, null); if(hitObj != null) { Vector3[] v = pb.VerticesInWorldSpace(q.indices); for(int i = 0; i < v.Length; i++) { if(null == RaycastFaceCheck(v[i], dir, dist, hitObj)) { hidden = false; break; } } } else hidden = false; return hidden; }
// breaks a pb_object into a zillion* faces public static GameObject[] ExplodeObject(pb_Object pb) { // disable 'ze donor pb.gameObject.SetActive(false); GameObject[] pieces = new GameObject[pb.faces.Length]; // extract mesh and material information for every face, and assign it to a gameobject for (int i = 0; i < pieces.Length; i++) { Mesh m = new Mesh(); m.vertices = pb.GetVertices(pb.faces[i]); m.triangles = new int[6] { 0, 1, 2, 1, 3, 2 }; m.normals = pb.GetNormals(pb.faces[i]); m.uv = pb.GetUVs(pb.faces[i]); m.RecalculateBounds(); GameObject go = new GameObject(); go.transform.position = pb.transform.position + pb_Math.PlaneNormal(m.vertices).normalized * .3f; go.transform.localRotation = pb.transform.localRotation; go.AddComponent <MeshFilter>().sharedMesh = m; go.AddComponent <MeshRenderer>().sharedMaterial = pb.GetMaterial(pb.faces[i]); pieces[i] = go; } return(pieces); }
// breaks a pb_object into a zillion* faces public static GameObject[] ExplodeObject(pb_Object pb) { // disable 'ze donor pb.gameObject.SetActive(false); GameObject[] pieces = new GameObject[pb.faces.Length]; // extract mesh and material information for every face, and assign it to a gameobject for(int i = 0; i < pieces.Length; i++) { Mesh m = new Mesh(); m.vertices = pb.GetVertices(pb.faces[i]); m.triangles = new int[6] {0,1,2, 1,3,2}; m.normals = pb.GetNormals(pb.faces[i]); m.uv = pb.GetUVs(pb.faces[i]); m.RecalculateBounds(); GameObject go = new GameObject(); go.transform.position = pb.transform.position + pb_Math.PlaneNormal(m.vertices).normalized * .3f; go.transform.localRotation = pb.transform.localRotation; go.AddComponent<MeshFilter>().sharedMesh = m; go.AddComponent<MeshRenderer>().sharedMaterial = pb.GetMaterial(pb.faces[i]); pieces[i] = go; } return pieces; }
public static bool HiddenFace(pb_Object pb, pb_Face q, float dist) { // Grab the face normal Vector3 dir = pb_Math.Normal(pb.VerticesInWorldSpace(q.indices)); // If casting from the center of the plane hits, chekc the rest of the points for collisions Vector3 orig = pb.transform.TransformPoint(pb_Math.Average(pb.GetVertices(q))); bool hidden = true; Transform hitObj = RaycastFaceCheck(orig, dir, dist, null); if (hitObj != null) { Vector3[] v = pb.VerticesInWorldSpace(q.indices); for (int i = 0; i < v.Length; i++) { if (null == RaycastFaceCheck(v[i], dir, dist, hitObj)) { hidden = false; break; } } } else { hidden = false; } return(hidden); }
/** * Returns requested vertices in world space coordinates. */ public static Vector3[] VerticesInWorldSpace(this pb_Object pb, int[] indices) { if (indices == null) { Debug.LogWarning("indices == null -> VerticesInWorldSpace"); } Vector3[] worldPoints = pb.GetVertices(indices); for (int i = 0; i < worldPoints.Length; i++) { worldPoints[i] = pb.transform.TransformPoint(worldPoints[i]); } return(worldPoints); }
/** * Projects UVs for each face using the closest normal on a box. */ public static void ProjectFacesBox(pb_Object pb, pb_Face[] faces) { Vector2[] uv = pb.uv; Dictionary <ProjectionAxis, List <pb_Face> > sorted = new Dictionary <ProjectionAxis, List <pb_Face> >(); for (int i = 0; i < faces.Length; i++) { Vector3 nrm = pb_Math.Normal(pb, faces[i]); ProjectionAxis axis = pb_Math.VectorToProjectionAxis(nrm); if (sorted.ContainsKey(axis)) { sorted[axis].Add(faces[i]); } else { sorted.Add(axis, new List <pb_Face>() { faces[i] }); } // clean up UV stuff - no shared UV indices and remove element group faces[i].elementGroup = -1; } foreach (KeyValuePair <ProjectionAxis, List <pb_Face> > kvp in sorted) { int[] distinct = pb_Face.AllTrianglesDistinct(kvp.Value.ToArray()); Vector2[] uvs = pb_Math.PlanarProject(pb.GetVertices(distinct), pb_Math.ProjectionAxisToVector(kvp.Key), kvp.Key); for (int n = 0; n < distinct.Length; n++) { uv[distinct[n]] = uvs[n]; } SplitUVs(pb, distinct); } /* and set the msh uv array using the new coordintaes */ pb.SetUV(uv); pb.ToMesh(); pb.Refresh(); }
/** * Attempt to figure out the winding order the passed face. Note that * this may return WindingOrder.Unknown. */ public static WindingOrder GetWindingOrder(this pb_Object pb, pb_Face face) { Vector2[] p = pb_Math.PlanarProject(pb.GetVertices(face.edges.AllTriangles()), pb_Math.Normal(pb, face)); float sum = 0f; // http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order for (int i = 0; i < p.Length; i++) { Vector2 a = p[i]; Vector2 b = i < p.Length - 1 ? p[i + 1] : p[0]; sum += ((b.x - a.x) * (b.y + a.y)); } return(sum == 0f ? WindingOrder.Unknown : (sum >= 0f ? WindingOrder.Clockwise : WindingOrder.CounterClockwise)); }
/** * Inserts a split from each selected vertex to the center of the face */ private static bool PokeFace_Internal(pb_Object pb, pb_Face face, int[] indices_nonFaceSpecific, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitSharedIndices = null; pb_IntArray[] sharedIndices = pb.sharedIndices; ///** Sort index array such that it only uses indices local to the passed face int[] indices = new int[indices_nonFaceSpecific.Length]; int[] dist_ind_si = new int[face.distinctIndices.Length]; // figure out sharedIndices index of distinct Indices for(int i = 0; i < face.distinctIndices.Length; i++) dist_ind_si[i] = sharedIndices.IndexOf(face.distinctIndices[i]); // now do the same for non-face specific indices, assigning matching groups for(int i = 0; i < indices.Length; i++) { int ind = System.Array.IndexOf(dist_ind_si, sharedIndices.IndexOf(indices_nonFaceSpecific[i])); if(ind < 0) return false; indices[i] = face.distinctIndices[ind]; } ///** Sort index array such that it only uses indices local to the passed face Vector3 cen3d = pb_Math.Average(pb.GetVertices(face)); Vector3[] verts = pb.GetVertices(face.distinctIndices); Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector2[] plane = pb_Math.VerticesTo2DPoints(verts, nrm); Vector2[] indPlane = pb_Math.VerticesTo2DPoints(pb.GetVertices(indices), nrm); Vector2 cen2d = pb_Math.VerticesTo2DPoints( new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[indices.Length]; for(int i = 0; i < indices.Length; i++) dividers[i] = (indPlane[i] - cen2d).normalized; List<Vector2>[] quadrants2d = new List<Vector2>[indices.Length]; List<Vector3>[] quadrants3d = new List<Vector3>[indices.Length]; List<int>[] sharedIndex = new List<int>[indices.Length]; for(int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List<Vector2>(1) { cen2d }; quadrants3d[i] = new List<Vector3>(1) { cen3d }; sharedIndex[i] = new List<int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } for(int i = 0; i < face.distinctIndices.Length; i++) { // if this index is a divider, it needs to belong to the leftmost and // rightmost quadrant int indexInPokeVerts = System.Array.IndexOf(indices, face.distinctIndices[i]); int ignore = -1; if( indexInPokeVerts > -1) { // Add vert to this quadrant quadrants2d[indexInPokeVerts].Add(plane[i]); quadrants3d[indexInPokeVerts].Add(verts[i]); sharedIndex[indexInPokeVerts].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); // And also the one closest counter clockwise ignore = indexInPokeVerts; } Vector2 dir = (plane[i]-cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for(int j = 0; j < dividers.Length; j++) { if(j == ignore) continue; // this is a dividing vertex - ignore float dist = Vector2.Angle(dividers[j], dir); if( Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f ) dist = 360f - dist; if(dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(plane[i]); quadrants3d[quad].Add(verts[i]); sharedIndex[quad].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for(int i = 0; i < len; i++) { tris[i] = Delauney.Triangulate(quadrants2d[i]).ToIntArray(); if(tris[i].Length < 3) return false; // todo - check that face normal is correct } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitSharedIndices = new int[len][]; for(int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, -1, face.color); splitVertices[i] = quadrants3d[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return true; }
void DrawStats(pb_Object pb) { StringBuilder sb = new StringBuilder(); Handles.BeginGUI(); if(edgeInfo) foreach(pb_Edge f in pb.SelectedEdges) { Vector2 cen = HandleUtility.WorldToGUIPoint( pb.transform.TransformPoint((pb.vertices[f.x] + pb.vertices[f.y])/ 2f) ); GUIContent gc = new GUIContent(f.ToString(), ""); DrawSceneLabel(gc, cen); } /** * SHARED INDICES */ // foreach(pb_IntArray arr in pb.sharedIndices) // { // Vector2 cen = HandleUtility.WorldToGUIPoint( pb.transform.TransformPoint(pb.vertices[arr[0]]) ); // GUI.Label(new Rect(cen.x, cen.y, 200, 200), ((int[])arr).ToFormattedString("\n")); // } if(faceInfo) foreach(pb_Face f in pb.SelectedFaces) { Vector2 cen = HandleUtility.WorldToGUIPoint( pb.transform.TransformPoint( pb_Math.Average( pb.GetVertices(f.distinctIndices) ) ) ); GUIContent gc = new GUIContent("Face: " + f.ToString(), ""); if(smoothingGroupInfo || elementGroupInfo || textureGroupInfo) gc.text += "\nGroups:"; if(smoothingGroupInfo) gc.text += "\nSmoothing: " + f.smoothingGroup; if(elementGroupInfo) gc.text += "\nElement: " + f.elementGroup; if(textureGroupInfo) gc.text += "\nTexture: " + f.textureGroup; DrawSceneLabel(gc, cen); } // sb.AppendLine(f.ToString() + ", "); // foreach(pb_Face face in pb.SelectedFaces) // sb.AppendLine(face.colors.ToFormattedString("\n") + "\n"); // sb.AppendLine("\n"); // foreach(pb_IntArray si in pb.sharedIndices) // { // sb.AppendLine(si.array.ToFormattedString(", ")); // } // sb.AppendLine("\n"); // if(vertexInfo) // { // try // { // Camera cam = SceneView.lastActiveSceneView.camera; // Vector3[] normals = pb.msh.normals; // int index = 0; // foreach(pb_IntArray arr in pb.sharedIndices) // { // Vector3 v = pb.transform.TransformPoint(pb.vertices[arr[0]] + normals[arr[0]] * .01f); // if(!pb_HandleUtility.PointIsOccluded(cam, pb, v)) // { // Vector2 cen = HandleUtility.WorldToGUIPoint( v ); // GUIContent gc = new GUIContent(index++ + ": " + arr.array.ToFormattedString(", "), ""); // DrawSceneLabel(gc, cen); // } // } // } catch { /* do not care */; } // } Handles.EndGUI(); Handles.BeginGUI(); { GUI.Label(new Rect(10, 10, 400, 800), sb.ToString()); } Handles.EndGUI(); }
// todo - there's a lot of duplicate code between this and poke face. /** * Inserts a vertex at the center of each edge, then connects the new vertices to another new * vertex placed at the center of the face. */ // internal method - so it's allow to be messy, right? private static bool SubdivideFace_Internal(pb_Object pb, EdgeConnection edgeConnection, out Vector3?[] appendedVertices, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitSharedIndices = null; appendedVertices = new Vector3?[edgeConnection.edges.Count]; // cache all the things pb_Face face = edgeConnection.face; pb_IntArray[] sharedIndices = pb.sharedIndices; Vector3[] vertices = pb.vertices; List <Vector3> edgeCenters3d = new List <Vector3>(); //pb.GetVertices(edgeConnection.face)); // filter duplicate edges int u = 0; List <int> usedEdgeIndices = new List <int>(); foreach (pb_Edge edge in edgeConnection.edges) { int ind = face.edges.IndexOf(edge, sharedIndices); if (!usedEdgeIndices.Contains(ind)) { Vector3 cen = (vertices[edge.x] + vertices[edge.y]) / 2f; edgeCenters3d.Add(cen); usedEdgeIndices.Add(ind); appendedVertices[u] = cen; } else { appendedVertices[u] = null; } u++; } // now we have all the vertices of the old face, plus the new edge center vertices Vector3[] verts3d = pb.GetVertices(face.distinctIndices); Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector2[] verts2d = pb_Math.VerticesTo2DPoints(verts3d, nrm); Vector2[] edgeCenters2d = pb_Math.VerticesTo2DPoints(edgeCenters3d.ToArray(), nrm); Vector3 cen3d = pb_Math.Average(verts3d); Vector2 cen2d = pb_Math.VerticesTo2DPoints(new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[edgeCenters2d.Length]; for (int i = 0; i < edgeCenters2d.Length; i++) { dividers[i] = (edgeCenters2d[i] - cen2d).normalized; } List <Vector2>[] quadrants2d = new List <Vector2> [edgeCenters2d.Length]; List <Vector3>[] quadrants3d = new List <Vector3> [edgeCenters2d.Length]; List <int>[] sharedIndex = new List <int> [edgeCenters2d.Length]; for (int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List <Vector2>(1) { cen2d }; quadrants3d[i] = new List <Vector3>(1) { cen3d }; sharedIndex[i] = new List <int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } // add the divisors for (int i = 0; i < edgeCenters2d.Length; i++) { quadrants2d[i].Add(edgeCenters2d[i]); quadrants3d[i].Add(edgeCenters3d[i]); sharedIndex[i].Add(-1); // -(i+2) to group new vertices in AppendFace // and add closest in the counterclockwise direction Vector2 dir = (edgeCenters2d[i] - cen2d).normalized; float largestClockwiseDistance = 0f; int quad = -1; for (int j = 0; j < dividers.Length; j++) { if (j == i) { continue; // this is a dividing vertex - ignore } float dist = Vector2.Angle(dividers[j], dir); if (Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f) { dist = 360f - dist; } if (dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(edgeCenters2d[i]); quadrants3d[quad].Add(edgeCenters3d[i]); sharedIndex[quad].Add(-1); } // distribute the existing vertices for (int i = 0; i < face.distinctIndices.Length; i++) { Vector2 dir = (verts2d[i] - cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for (int j = 0; j < dividers.Length; j++) { float dist = Vector2.Angle(dividers[j], dir); if (Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f) { dist = 360f - dist; } if (dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(verts2d[i]); quadrants3d[quad].Add(verts3d[i]); sharedIndex[quad].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for (int i = 0; i < len; i++) { if (quadrants2d[i].Count < 3) { Debug.LogError("Insufficient points to triangulate - bailing on subdivide operation. This is probably due to a concave face, or maybe the compiler just doesn't like you today. 50/50 odds really."); return(false); } tris[i] = Delauney.Triangulate(quadrants2d[i]).ToIntArray(); Vector3[] nrm_check = new Vector3[3] { quadrants3d[i][tris[i][0]], quadrants3d[i][tris[i][1]], quadrants3d[i][tris[i][2]] }; if (Vector3.Dot(nrm, pb_Math.Normal(nrm_check)) < 0) { System.Array.Reverse(tris[i]); } } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitSharedIndices = new int[len][]; for (int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.color); splitVertices[i] = quadrants3d[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return(true); }
// todo - there's a lot of duplicate code between this and poke face. /** * Inserts a vertex at the center of each edge, then connects the new vertices to another new * vertex placed at the center of the face. */ // internal method - so it's allow to be messy, right? private static bool SubdivideFace_Internal(pb_Object pb, EdgeConnection edgeConnection, out Vector3?[] appendedVertices, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitSharedIndices = null; appendedVertices = new Vector3?[edgeConnection.edges.Count]; // cache all the things pb_Face face = edgeConnection.face; pb_IntArray[] sharedIndices = pb.sharedIndices; Vector3[] vertices = pb.vertices; List<Vector3> edgeCenters3d = new List<Vector3>();//pb.GetVertices(edgeConnection.face)); // filter duplicate edges int u = 0; List<int> usedEdgeIndices = new List<int>(); foreach(pb_Edge edge in edgeConnection.edges) { int ind = face.edges.IndexOf(edge, sharedIndices); if(!usedEdgeIndices.Contains(ind)) { Vector3 cen = (vertices[edge.x] + vertices[edge.y]) / 2f; edgeCenters3d.Add(cen); usedEdgeIndices.Add(ind); appendedVertices[u] = cen; } else appendedVertices[u] = null; u++; } // now we have all the vertices of the old face, plus the new edge center vertices Vector3[] verts3d = pb.GetVertices(face.distinctIndices); Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector2[] verts2d = pb_Math.VerticesTo2DPoints(verts3d, nrm); Vector2[] edgeCenters2d = pb_Math.VerticesTo2DPoints(edgeCenters3d.ToArray(), nrm); Vector3 cen3d = pb_Math.Average(verts3d); Vector2 cen2d = pb_Math.VerticesTo2DPoints( new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[edgeCenters2d.Length]; for(int i = 0; i < edgeCenters2d.Length; i++) dividers[i] = (edgeCenters2d[i] - cen2d).normalized; List<Vector2>[] quadrants2d = new List<Vector2>[edgeCenters2d.Length]; List<Vector3>[] quadrants3d = new List<Vector3>[edgeCenters2d.Length]; List<int>[] sharedIndex = new List<int>[edgeCenters2d.Length]; for(int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List<Vector2>(1) { cen2d }; quadrants3d[i] = new List<Vector3>(1) { cen3d }; sharedIndex[i] = new List<int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } // add the divisors for(int i = 0; i < edgeCenters2d.Length; i++) { quadrants2d[i].Add(edgeCenters2d[i]); quadrants3d[i].Add(edgeCenters3d[i]); sharedIndex[i].Add(-1); // -(i+2) to group new vertices in AppendFace // and add closest in the counterclockwise direction Vector2 dir = (edgeCenters2d[i]-cen2d).normalized; float largestClockwiseDistance = 0f; int quad = -1; for(int j = 0; j < dividers.Length; j++) { if(j == i) continue; // this is a dividing vertex - ignore float dist = Vector2.Angle(dividers[j], dir); if( Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f ) dist = 360f - dist; if(dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(edgeCenters2d[i]); quadrants3d[quad].Add(edgeCenters3d[i]); sharedIndex[quad].Add(-1); } // distribute the existing vertices for(int i = 0; i < face.distinctIndices.Length; i++) { Vector2 dir = (verts2d[i]-cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for(int j = 0; j < dividers.Length; j++) { float dist = Vector2.Angle(dividers[j], dir); if( Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f ) dist = 360f - dist; if(dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(verts2d[i]); quadrants3d[quad].Add(verts3d[i]); sharedIndex[quad].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for(int i = 0; i < len; i++) { if(quadrants2d[i].Count < 3) { Debug.LogError("Insufficient points to triangulate - bailing on subdivide operation. This is probably due to a concave face, or maybe the compiler just doesn't like you today. 50/50 odds really."); return false; } tris[i] = Delauney.Triangulate(quadrants2d[i]).ToIntArray(); Vector3[] nrm_check = new Vector3[3] { quadrants3d[i][tris[i][0]], quadrants3d[i][tris[i][1]], quadrants3d[i][tris[i][2]] }; if( Vector3.Dot(nrm, pb_Math.Normal(nrm_check)) < 0 ) System.Array.Reverse(tris[i]); } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitSharedIndices = new int[len][]; for(int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.color); splitVertices[i] = quadrants3d[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return true; }
/** * 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); }
/** * Inserts a vertex at the center of each edge, then connects the new vertices to another new * vertex placed at the center of the face. */ private static bool SubdivideFace_Internal(pb_Object pb, pb_EdgeConnection pb_edgeConnection, out DanglingVertex?[] appendedVertices, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out Color[][] splitColors, out Vector2[][] splitUVs, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitColors = null; splitUVs = null; splitSharedIndices = null; appendedVertices = new DanglingVertex?[pb_edgeConnection.edges.Count]; // cache all the things pb_Face face = pb_edgeConnection.face; Dictionary <int, int> sharedIndices = pb.sharedIndices.ToDictionary(); Vector3[] vertices = pb.vertices; Vector2[] uvs = pb.uv; List <Vector2> edgeCentersUV = new List <Vector2>(); List <Vector3> edgeCenters3d = new List <Vector3>(); List <Color> edgeCentersCol = new List <Color>(); // filter duplicate edges int u = 0; List <int> usedEdgeIndices = new List <int>(); foreach (pb_Edge edge in pb_edgeConnection.edges) { int ind = face.edges.IndexOf(edge, sharedIndices); if (!usedEdgeIndices.Contains(ind)) { Vector3 cen = (vertices[edge.x] + vertices[edge.y]) / 2f; appendedVertices[u] = new DanglingVertex(cen, (pb.colors[edge.x] + pb.colors[edge.y]) / 2f); edgeCenters3d.Add(cen); edgeCentersUV.Add((uvs[edge.x] + uvs[edge.y]) / 2f); edgeCentersCol.Add((pb.colors[edge.x] + pb.colors[edge.y]) / 2f); usedEdgeIndices.Add(ind); } else { appendedVertices[u] = null; } u++; } // now we have all the vertices of the old face, plus the new edge center vertices Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector3[] verts3d = pb.GetVertices(face.distinctIndices); Vector2[] faceUVs = pb.GetUVs(face.distinctIndices); Color[] colors = pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices); Vector2[] verts2d = pb_Math.PlanarProject(verts3d, nrm); Vector2[] edgeCenters2d = pb_Math.PlanarProject(edgeCenters3d.ToArray(), nrm); Vector3 cen3d = pb_Math.Average(verts3d); Vector2 cenUV = pb_Bounds2D.Center(faceUVs); Vector2 cen2d = pb_Math.PlanarProject(new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[edgeCenters2d.Length]; for (int i = 0; i < edgeCenters2d.Length; i++) { dividers[i] = (edgeCenters2d[i] - cen2d).normalized; } List <Vector2>[] quadrants2d = new List <Vector2> [edgeCenters2d.Length]; List <Vector3>[] quadrants3d = new List <Vector3> [edgeCenters2d.Length]; List <Vector2>[] quadrantsUV = new List <Vector2> [edgeCenters2d.Length]; List <Color>[] quadrantsCol = new List <Color> [edgeCenters2d.Length]; List <int>[] sharedIndex = new List <int> [edgeCenters2d.Length]; for (int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List <Vector2>(1) { cen2d }; quadrants3d[i] = new List <Vector3>(1) { cen3d }; quadrantsUV[i] = new List <Vector2>(1) { cenUV }; quadrantsCol[i] = new List <Color>(1) { pb_Math.Average(pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices)) }; sharedIndex[i] = new List <int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } // add the divisors for (int i = 0; i < edgeCenters2d.Length; i++) { quadrants2d[i].Add(edgeCenters2d[i]); quadrants3d[i].Add(edgeCenters3d[i]); quadrantsUV[i].Add(edgeCentersUV[i]); quadrantsCol[i].Add(edgeCentersCol[i]); sharedIndex[i].Add(-1); // and add closest in the counterclockwise direction Vector2 dir = (edgeCenters2d[i] - cen2d).normalized; float largestClockwiseDistance = 0f; int quad = -1; for (int j = 0; j < dividers.Length; j++) { if (j == i) { continue; // this is a dividing vertex - ignore } float dist = Vector2.Angle(dividers[j], dir); if (Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f) { dist = 360f - dist; } if (dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(edgeCenters2d[i]); quadrants3d[quad].Add(edgeCenters3d[i]); quadrantsUV[quad].Add(edgeCentersUV[i]); quadrantsCol[quad].Add(edgeCentersCol[i]); sharedIndex[quad].Add(-1); } // distribute the existing vertices for (int i = 0; i < face.distinctIndices.Length; i++) { Vector2 dir = (verts2d[i] - cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for (int j = 0; j < dividers.Length; j++) { float dist = Vector2.Angle(dividers[j], dir); if (Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f) { dist = 360f - dist; } if (dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(verts2d[i]); quadrants3d[quad].Add(verts3d[i]); quadrantsUV[quad].Add(faceUVs[i]); quadrantsCol[quad].Add(colors[i]); sharedIndex[quad].Add(sharedIndices[face.distinctIndices[i]]); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for (int i = 0; i < len; i++) { if (quadrants2d[i].Count < 3) { Debug.LogError("Insufficient points to triangulate. Exit subdivide operation. This is probably due to a concave face."); return(false); } tris[i] = Delaunay.Triangulate(quadrants2d[i]).ToIntArray(); if (tris[i].Length < 3) ///< #521 { return(false); } if (Vector3.Dot(nrm, pb_Math.Normal(quadrants3d[i][tris[i][0]], quadrants3d[i][tris[i][1]], quadrants3d[i][tris[i][2]])) < 0) { System.Array.Reverse(tris[i]); } } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitColors = new Color[len][]; splitUVs = new Vector2[len][]; splitSharedIndices = new int[len][]; for (int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.manualUV); splitVertices[i] = quadrants3d[i].ToArray(); splitColors[i] = quadrantsCol[i].ToArray(); splitUVs[i] = quadrantsUV[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return(true); }
/** * Splits face per vertex. * Todo - Could implement more sanity checks - namely testing for edges before sending to Split_Internal. However, * the split method is smart enough to fail on those cases, so ignore for now. */ public static bool ConnectVertices(this pb_Object pb, List <pb_VertexConnection> vertexConnectionsUnfiltered, out int[] triangles) { List <pb_VertexConnection> vertexConnections = new List <pb_VertexConnection>(); List <int> inds = new List <int>(); int i = 0; for (i = 0; i < vertexConnectionsUnfiltered.Count; i++) { pb_VertexConnection vc = vertexConnectionsUnfiltered[i]; vc.indices = vc.indices.Distinct().ToList(); if (vc.isValid) { inds.AddRange(vc.indices); vertexConnections.Add(vc); } } if (vertexConnections.Count < 1) { triangles = null; return(false); } List <Vector3> selectedVertices = pb.GetVertices(pb_VertexConnection.AllTriangles(vertexConnections)); int len = vertexConnections.Count; // new faces will be built from successfull split ops List <pb_Face> successfullySplitFaces = new List <pb_Face>(); List <pb_Face> all_splitFaces = new List <pb_Face>(); List <Vector3[]> all_splitVertices = new List <Vector3[]>(); List <Color[]> all_splitColors = new List <Color[]>(); List <Vector2[]> all_splitUVs = new List <Vector2[]>(); List <int[]> all_splitSharedIndices = new List <int[]>(); bool[] success = new bool[len]; pb_IntArray[] sharedIndices = pb.sharedIndices; i = 0; foreach (pb_VertexConnection vc in vertexConnections) { pb_Face[] splitFaces = null; Vector3[][] splitVertices = null; Color[][] splitColors = null; Vector2[][] splitUVs; int[][] splitSharedIndices = null; if (vc.indices.Count < 3) { int indA = vc.face.indices.IndexOf(vc.indices[0], sharedIndices); int indB = vc.face.indices.IndexOf(vc.indices[1], sharedIndices); if (indA < 0 || indB < 0) { success[i] = false; continue; } indA = vc.face.indices[indA]; indB = vc.face.indices[indB]; success[i] = SplitFace_Internal( new SplitSelection(pb, vc.face, pb.vertices[indA], pb.vertices[indB], pb.colors[indA], pb.colors[indB], true, true, new int[] { indA }, new int[] { indB }), out splitFaces, out splitVertices, out splitColors, out splitUVs, out splitSharedIndices); if (success[i]) { successfullySplitFaces.Add(vc.face); } } else { Vector3 pokedVertex; success[i] = PokeFace_Internal(pb, vc.face, vc.indices.ToArray(), out pokedVertex, out splitFaces, out splitVertices, out splitColors, out splitUVs, out splitSharedIndices); if (success[i]) { selectedVertices.Add(pokedVertex); successfullySplitFaces.Add(vc.face); } } if (success[i]) { int texGroup = pb.UnusedTextureGroup(i + 1); for (int j = 0; j < splitFaces.Length; j++) { splitFaces[j].textureGroup = texGroup; all_splitFaces.Add(splitFaces[j]); all_splitVertices.Add(splitVertices[j]); all_splitColors.Add(splitColors[j]); all_splitUVs.Add(splitUVs[j]); all_splitSharedIndices.Add(splitSharedIndices[j]); } } i++; } if (all_splitFaces.Count < 1) { triangles = null; return(false); } pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitColors.ToArray(), all_splitUVs.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray()); inds.AddRange(pb_Face.AllTriangles(appendedFaces)); int[] welds; pb.WeldVertices(inds.ToArray(), Mathf.Epsilon, out welds); pb.DeleteFaces(successfullySplitFaces.ToArray()); List <int> seltris = new List <int>(); for (i = 0; i < selectedVertices.Count; i++) { int ind = System.Array.IndexOf(pb.vertices, selectedVertices[i]); if (ind > -1) { seltris.Add(ind); } } triangles = seltris.Distinct().ToArray(); return(true); }
/** * This method assumes that the split selection edges share a common face and have already been sanity checked. Will return * the variables necessary to compose a new face from the split, or null if the split is invalid. */ private static bool SplitFace_Internal(SplitSelection splitSelection, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out Color[][] splitColors, out Vector2[][] splitUVs, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitColors = null; splitUVs = null; splitSharedIndices = null; pb_Object pb = splitSelection.pb; // we'll be using this a lot pb_Face face = splitSelection.face; // likewise int[] indices = face.distinctIndices; pb_IntArray[] sharedIndices = pb.sharedIndices; int[] sharedIndex = new int[indices.Length]; for (int i = 0; i < indices.Length; i++) { sharedIndex[i] = sharedIndices.IndexOf(indices[i]); } // First order of business is to translate the face to 2D plane. Vector3[] verts = pb.GetVertices(face.distinctIndices); Color[] colors = pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices); Vector2[] uvs = pb.GetUVs(face.distinctIndices); Vector3 projAxis = pb_Math.ProjectionAxisToVector(pb_Math.VectorToProjectionAxis(pb_Math.Normal(pb, face))); Vector2[] plane = pb_Math.PlanarProject(verts, projAxis); // Split points Vector3 splitPointA_3d = splitSelection.pointA; Vector3 splitPointB_3d = splitSelection.pointB; Vector2 splitPointA_uv = splitSelection.aIsVertex ? pb.uv[splitSelection.indexA[0]] : (pb.uv[splitSelection.indexA[0]] + pb.uv[splitSelection.indexA[1]]) / 2f; Vector2 splitPointB_uv = splitSelection.bIsVertex ? pb.uv[splitSelection.indexB[0]] : (pb.uv[splitSelection.indexB[0]] + pb.uv[splitSelection.indexB[1]]) / 2f; Vector2 splitPointA_2d = pb_Math.PlanarProject(new Vector3[1] { splitPointA_3d }, projAxis)[0]; Vector2 splitPointB_2d = pb_Math.PlanarProject(new Vector3[1] { splitPointB_3d }, projAxis)[0]; List <Vector3> v_polyA = new List <Vector3>(); // point in object space List <Vector3> v_polyB = new List <Vector3>(); // point in object space List <Color> c_polyA = new List <Color>(); List <Color> c_polyB = new List <Color>(); List <Vector2> v_polyB_2d = new List <Vector2>(); // point in 2d space - used to triangulate List <Vector2> v_polyA_2d = new List <Vector2>(); // point in 2d space - used to triangulate List <Vector2> u_polyA = new List <Vector2>(); List <Vector2> u_polyB = new List <Vector2>(); List <int> i_polyA = new List <int>(); // sharedIndices array index List <int> i_polyB = new List <int>(); // sharedIndices array index List <int> nedgeA = new List <int>(); List <int> nedgeB = new List <int>(); // Sort points into two separate polygons for (int i = 0; i < indices.Length; i++) { // is this point (a) a vertex to split or (b) on the negative or positive side of this split line if ((splitSelection.aIsVertex && splitSelection.indexA[0] == indices[i]) || (splitSelection.bIsVertex && splitSelection.indexB[0] == indices[i])) { v_polyA.Add(verts[i]); v_polyB.Add(verts[i]); u_polyA.Add(uvs[i]); u_polyB.Add(uvs[i]); v_polyA_2d.Add(plane[i]); v_polyB_2d.Add(plane[i]); i_polyA.Add(sharedIndex[i]); i_polyB.Add(sharedIndex[i]); c_polyA.Add(colors[i]); c_polyB.Add(colors[i]); } else { // split points across the division line Vector2 perp = pb_Math.Perpendicular(splitPointB_2d, splitPointA_2d); Vector2 origin = (splitPointA_2d + splitPointB_2d) / 2f; if (Vector2.Dot(perp, plane[i] - origin) > 0) { v_polyA.Add(verts[i]); v_polyA_2d.Add(plane[i]); u_polyA.Add(uvs[i]); i_polyA.Add(sharedIndex[i]); c_polyA.Add(colors[i]); } else { v_polyB.Add(verts[i]); v_polyB_2d.Add(plane[i]); u_polyB.Add(uvs[i]); i_polyB.Add(sharedIndex[i]); c_polyB.Add(colors[i]); } } } if (!splitSelection.aIsVertex) { v_polyA.Add(splitPointA_3d); v_polyA_2d.Add(splitPointA_2d); u_polyA.Add(splitPointA_uv); i_polyA.Add(-1); c_polyA.Add(splitSelection.colorA); v_polyB.Add(splitPointA_3d); v_polyB_2d.Add(splitPointA_2d); u_polyB.Add(splitPointA_uv); i_polyB.Add(-1); // neg 1 because it's a new vertex point c_polyB.Add(splitSelection.colorA); nedgeA.Add(v_polyA.Count); nedgeB.Add(v_polyB.Count); } // PLACE if (!splitSelection.bIsVertex) { v_polyA.Add(splitPointB_3d); v_polyA_2d.Add(splitPointB_2d); u_polyA.Add(splitPointB_uv); i_polyA.Add(-1); c_polyB.Add(splitSelection.colorB); v_polyB.Add(splitPointB_3d); v_polyB_2d.Add(splitPointB_2d); u_polyB.Add(splitPointB_uv); i_polyB.Add(-1); // neg 1 because it's a new vertex point c_polyB.Add(splitSelection.colorB); nedgeA.Add(v_polyA.Count); nedgeB.Add(v_polyB.Count); } if (v_polyA_2d.Count < 3 || v_polyB_2d.Count < 3) { splitFaces = null; splitVertices = null; splitSharedIndices = null; return(false); } // triangulate new polygons int[] t_polyA = Delaunay.Triangulate(v_polyA_2d).ToIntArray(); int[] t_polyB = Delaunay.Triangulate(v_polyB_2d).ToIntArray(); if (t_polyA.Length < 3 || t_polyB.Length < 3) { return(false); } // figure out the face normals for the new faces and check to make sure they match the original face Vector2[] pln = pb_Math.PlanarProject(pb.GetVertices(face.indices), projAxis); Vector3 nrm = Vector3.Cross(pln[2] - pln[0], pln[1] - pln[0]); Vector3 nrmA = Vector3.Cross(v_polyA_2d[t_polyA[2]] - v_polyA_2d[t_polyA[0]], v_polyA_2d[t_polyA[1]] - v_polyA_2d[t_polyA[0]]); Vector3 nrmB = Vector3.Cross(v_polyB_2d[t_polyB[2]] - v_polyB_2d[t_polyB[0]], v_polyB_2d[t_polyB[1]] - v_polyB_2d[t_polyB[0]]); if (Vector3.Dot(nrm, nrmA) < 0) { System.Array.Reverse(t_polyA); } if (Vector3.Dot(nrm, nrmB) < 0) { System.Array.Reverse(t_polyB); } // triangles, material, pb_UV, smoothing group, shared index pb_Face faceA = new pb_Face(t_polyA, face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.manualUV); pb_Face faceB = new pb_Face(t_polyB, face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.manualUV); splitFaces = new pb_Face[2] { faceA, faceB }; splitVertices = new Vector3[2][] { v_polyA.ToArray(), v_polyB.ToArray() }; splitColors = new Color[2][] { c_polyA.ToArray(), c_polyB.ToArray() }; splitUVs = new Vector2[2][] { u_polyA.ToArray(), u_polyB.ToArray() }; splitSharedIndices = new int[2][] { i_polyA.ToArray(), i_polyB.ToArray() }; return(true); }
/** * Projects UVs for each face using the closest normal on a box. */ public static void ProjectFacesBox(pb_Object pb, pb_Face[] faces) { Vector2[] uv = pb.uv; Dictionary<ProjectionAxis, List<pb_Face>> sorted = new Dictionary<ProjectionAxis, List<pb_Face>>(); for(int i = 0; i < faces.Length; i++) { Vector3 nrm = pb_Math.Normal(pb, faces[i]); ProjectionAxis axis = pb_Math.VectorToProjectionAxis(nrm); if(sorted.ContainsKey(axis)) { sorted[axis].Add(faces[i]); } else { sorted.Add(axis, new List<pb_Face>() { faces[i] }); } // clean up UV stuff - no shared UV indices and remove element group faces[i].elementGroup = -1; } foreach(KeyValuePair<ProjectionAxis, List<pb_Face>> kvp in sorted) { int[] distinct = pb_Face.AllTrianglesDistinct(kvp.Value.ToArray()); Vector2[] uvs = pb_Math.PlanarProject( pb.GetVertices(distinct), pb_Math.ProjectionAxisToVector(kvp.Key), kvp.Key ); for(int n = 0; n < distinct.Length; n++) uv[distinct[n]] = uvs[n]; SplitUVs(pb, distinct); } /* and set the msh uv array using the new coordintaes */ pb.SetUV(uv); pb.ToMesh(); pb.Refresh(); }
/** * Inserts a split from each selected vertex to the center of the face */ private static bool PokeFace_Internal(pb_Object pb, pb_Face face, int[] indices_nonFaceSpecific, out Vector3 pokedVertex, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out Color[][] splitColors, out Vector2[][] splitUVs, out int[][] splitSharedIndices) { pokedVertex = Vector3.zero; splitFaces = null; splitVertices = null; splitColors = null; splitUVs = null; splitSharedIndices = null; pb_IntArray[] sharedIndices = pb.sharedIndices; ///** Sort index array such that it only uses indices local to the passed face int[] dist_indices = new int[indices_nonFaceSpecific.Length]; int[] dist_ind_si = new int[face.distinctIndices.Length]; // figure out sharedIndices index of distinct Indices for (int i = 0; i < face.distinctIndices.Length; i++) { dist_ind_si[i] = sharedIndices.IndexOf(face.distinctIndices[i]); } // now do the same for non-face specific indices, assigning matching groups ///** Sort index array such that it only uses indices local to the passed face for (int i = 0; i < dist_indices.Length; i++) { int ind = System.Array.IndexOf(dist_ind_si, sharedIndices.IndexOf(indices_nonFaceSpecific[i])); if (ind < 0) { return(false); } dist_indices[i] = face.distinctIndices[ind]; } int[] indices = dist_indices.Distinct().ToArray(); // throw out splits with less than 2 vertices, or splits composed // of a single edge switch (indices.Length) { case 0: case 1: return(false); case 2: if (System.Array.IndexOf(face.edges, new pb_Edge(indices[0], indices[1])) > -1) { return(false); } break; default: break; } // end triangle sorting /** * The general idea here is to project the face into 2d space, * split the 2d points into groups based on the intersecting lines, * then triangulate those groups. once the groups have been * triangulated, rebuild the 3d vertices using the new groups * (building new verts for seams). * * Think like you're cutting a pie... but first the pie is a basketball, * then a pie, then a basketball again. I'm on a horse. */ Vector3[] verts = pb.GetVertices(face.distinctIndices); Vector2[] uvs = pb.GetUVs(face.distinctIndices); Color[] colors = pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices); Vector2 cenUV = pb_Bounds2D.Center(uvs); Vector3 cen3d = pb_Math.Average(verts); pokedVertex = cen3d; Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Color cenColor = pb_Math.Average(colors); // this should be cleaned up Vector2[] plane = pb_Math.PlanarProject(verts, nrm); Vector2[] indPlane = pb_Math.PlanarProject(pb.GetVertices(indices), nrm); Vector2 cen2d = pb_Math.PlanarProject(new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[indices.Length]; for (int i = 0; i < indices.Length; i++) { dividers[i] = (indPlane[i] - cen2d).normalized; } List <Vector2>[] quadrants2d = new List <Vector2> [indices.Length]; List <Vector3>[] quadrants3d = new List <Vector3> [indices.Length]; List <Vector2>[] quadrantsUV_2d = new List <Vector2> [indices.Length]; List <Color>[] quadrantsCol = new List <Color> [indices.Length]; List <int>[] sharedIndex = new List <int> [indices.Length]; for (int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List <Vector2>(1) { cen2d }; quadrants3d[i] = new List <Vector3>(1) { cen3d }; quadrantsUV_2d[i] = new List <Vector2>(1) { cenUV }; quadrantsCol[i] = new List <Color>(1) { cenColor }; sharedIndex[i] = new List <int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } for (int i = 0; i < face.distinctIndices.Length; i++) { // if this index is a divider, it needs to belong to the leftmost and // rightmost quadrant int indexInPokeVerts = System.Array.IndexOf(indices, face.distinctIndices[i]); int ignore = -1; if (indexInPokeVerts > -1) { // Add vert to this quadrant quadrants2d[indexInPokeVerts].Add(plane[i]); quadrants3d[indexInPokeVerts].Add(verts[i]); quadrantsUV_2d[indexInPokeVerts].Add(uvs[i]); quadrantsCol[indexInPokeVerts].Add(colors[i]); sharedIndex[indexInPokeVerts].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); // And also the one closest counter clockwise ignore = indexInPokeVerts; } Vector2 dir = (plane[i] - cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for (int j = 0; j < dividers.Length; j++) { if (j == ignore) { continue; // this is a dividing vertex - ignore } float dist = Vector2.Angle(dividers[j], dir); if (Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f) { dist = 360f - dist; } if (dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(plane[i]); quadrants3d[quad].Add(verts[i]); quadrantsUV_2d[quad].Add(uvs[i]); quadrantsCol[quad].Add(colors[i]); sharedIndex[quad].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for (int i = 0; i < len; i++) { try { tris[i] = Delaunay.Triangulate(quadrants2d[i]).ToIntArray(); if (tris[i] == null || tris[i].Length < 3) { Debug.Log("Fail triangulation"); return(false); } } catch (System.Exception error) { Debug.LogError("PokeFace internal failed triangulation. Bail!\n" + error); return(false); } // todo - check that face normal is correct } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitColors = new Color[len][]; splitUVs = new Vector2[len][]; splitSharedIndices = new int[len][]; for (int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); splitVertices[i] = quadrants3d[i].ToArray(); splitColors[i] = quadrantsCol[i].ToArray(); splitUVs[i] = quadrantsUV_2d[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return(true); }
/** * Inserts a split from each selected vertex to the center of the face */ private static bool PokeFace_Internal(pb_Object pb, pb_Face face, int[] indices_nonFaceSpecific, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitSharedIndices = null; pb_IntArray[] sharedIndices = pb.sharedIndices; ///** Sort index array such that it only uses indices local to the passed face int[] indices = new int[indices_nonFaceSpecific.Length]; int[] dist_ind_si = new int[face.distinctIndices.Length]; // figure out sharedIndices index of distinct Indices for (int i = 0; i < face.distinctIndices.Length; i++) { dist_ind_si[i] = sharedIndices.IndexOf(face.distinctIndices[i]); } // now do the same for non-face specific indices, assigning matching groups for (int i = 0; i < indices.Length; i++) { int ind = System.Array.IndexOf(dist_ind_si, sharedIndices.IndexOf(indices_nonFaceSpecific[i])); if (ind < 0) { return(false); } indices[i] = face.distinctIndices[ind]; } ///** Sort index array such that it only uses indices local to the passed face Vector3 cen3d = pb_Math.Average(pb.GetVertices(face)); Vector3[] verts = pb.GetVertices(face.distinctIndices); Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector2[] plane = pb_Math.VerticesTo2DPoints(verts, nrm); Vector2[] indPlane = pb_Math.VerticesTo2DPoints(pb.GetVertices(indices), nrm); Vector2 cen2d = pb_Math.VerticesTo2DPoints(new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[indices.Length]; for (int i = 0; i < indices.Length; i++) { dividers[i] = (indPlane[i] - cen2d).normalized; } List <Vector2>[] quadrants2d = new List <Vector2> [indices.Length]; List <Vector3>[] quadrants3d = new List <Vector3> [indices.Length]; List <int>[] sharedIndex = new List <int> [indices.Length]; for (int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List <Vector2>(1) { cen2d }; quadrants3d[i] = new List <Vector3>(1) { cen3d }; sharedIndex[i] = new List <int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } for (int i = 0; i < face.distinctIndices.Length; i++) { // if this index is a divider, it needs to belong to the leftmost and // rightmost quadrant int indexInPokeVerts = System.Array.IndexOf(indices, face.distinctIndices[i]); int ignore = -1; if (indexInPokeVerts > -1) { // Add vert to this quadrant quadrants2d[indexInPokeVerts].Add(plane[i]); quadrants3d[indexInPokeVerts].Add(verts[i]); sharedIndex[indexInPokeVerts].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); // And also the one closest counter clockwise ignore = indexInPokeVerts; } Vector2 dir = (plane[i] - cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for (int j = 0; j < dividers.Length; j++) { if (j == ignore) { continue; // this is a dividing vertex - ignore } float dist = Vector2.Angle(dividers[j], dir); if (Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f) { dist = 360f - dist; } if (dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(plane[i]); quadrants3d[quad].Add(verts[i]); sharedIndex[quad].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for (int i = 0; i < len; i++) { tris[i] = Delauney.Triangulate(quadrants2d[i]).ToIntArray(); if (tris[i].Length < 3) { return(false); } // todo - check that face normal is correct } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitSharedIndices = new int[len][]; for (int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, -1, face.color); splitVertices[i] = quadrants3d[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return(true); }
void DrawStats(pb_Object pb) { StringBuilder sb = new StringBuilder(); Handles.BeginGUI(); if (edgeInfo) { foreach (pb_Edge f in pb.SelectedEdges) { Vector2 cen = HandleUtility.WorldToGUIPoint(pb.transform.TransformPoint((pb.vertices[f.x] + pb.vertices[f.y]) / 2f)); GUIContent gc = new GUIContent(f.ToString(), ""); DrawSceneLabel(gc, cen); } } /** * SHARED INDICES */ // foreach(pb_IntArray arr in pb.sharedIndices) // { // Vector2 cen = HandleUtility.WorldToGUIPoint( pb.transform.TransformPoint(pb.vertices[arr[0]]) ); // GUI.Label(new Rect(cen.x, cen.y, 200, 200), ((int[])arr).ToFormattedString("\n")); // } if (faceInfo) { foreach (pb_Face f in pb.SelectedFaces) { Vector2 cen = HandleUtility.WorldToGUIPoint(pb.transform.TransformPoint(pb_Math.Average(pb.GetVertices(f.distinctIndices)))); GUIContent gc = new GUIContent("Face: " + f.ToString(), ""); if (smoothingGroupInfo || elementGroupInfo || textureGroupInfo) { gc.text += "\nGroups:"; } if (smoothingGroupInfo) { gc.text += "\nSmoothing: " + f.smoothingGroup; } if (elementGroupInfo) { gc.text += "\nElement: " + f.elementGroup; } if (textureGroupInfo) { gc.text += "\nTexture: " + f.textureGroup; } DrawSceneLabel(gc, cen); } } // sb.AppendLine(f.ToString() + ", "); // foreach(pb_Face face in pb.SelectedFaces) // sb.AppendLine(face.colors.ToFormattedString("\n") + "\n"); // sb.AppendLine("\n"); // foreach(pb_IntArray si in pb.sharedIndices) // { // sb.AppendLine(si.array.ToFormattedString(", ")); // } // sb.AppendLine("\n"); // if(vertexInfo) // { // try // { // Camera cam = SceneView.lastActiveSceneView.camera; // Vector3[] normals = pb.msh.normals; // int index = 0; // foreach(pb_IntArray arr in pb.sharedIndices) // { // Vector3 v = pb.transform.TransformPoint(pb.vertices[arr[0]] + normals[arr[0]] * .01f); // if(!pb_HandleUtility.PointIsOccluded(cam, pb, v)) // { // Vector2 cen = HandleUtility.WorldToGUIPoint( v ); // GUIContent gc = new GUIContent(index++ + ": " + arr.array.ToFormattedString(", "), ""); // DrawSceneLabel(gc, cen); // } // } // } catch { /* do not care */; } // } Handles.EndGUI(); Handles.BeginGUI(); { GUI.Label(new Rect(10, 10, 400, 800), sb.ToString()); } Handles.EndGUI(); }
/** * Given a face and a point, this will add a vertex to the pb_Object and retriangulate the face. */ public static bool AppendVerticesToFace(this pb_Object pb, pb_Face face, Vector3[] points, Color[] addColors, out pb_Face newFace) { if (!face.IsValid()) { newFace = face; return(false); } // First order of business - project face to 2d int[] distinctIndices = face.distinctIndices; Vector3[] verts = pb.GetVertices(distinctIndices); Color[] cols = pbUtil.ValuesWithIndices(pb.colors, distinctIndices); Vector2[] uvs = new Vector2[distinctIndices.Length + points.Length]; System.Array.Copy(pb.GetUVs(distinctIndices), 0, uvs, 0, distinctIndices.Length); // Add the new point Vector3[] t_verts = new Vector3[verts.Length + points.Length]; System.Array.Copy(verts, 0, t_verts, 0, verts.Length); System.Array.Copy(points, 0, t_verts, verts.Length, points.Length); verts = t_verts; // Add the new color Color[] t_col = new Color[cols.Length + addColors.Length]; System.Array.Copy(cols, 0, t_col, 0, cols.Length); System.Array.Copy(addColors, 0, t_col, cols.Length, addColors.Length); cols = t_col; // Get the face normal before modifying the vertex array Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector3 projAxis = pb_Math.ProjectionAxisToVector(pb_Math.VectorToProjectionAxis(nrm)); // Project List <Vector2> plane = new List <Vector2>(pb_Math.PlanarProject(verts, projAxis)); // Save the sharedIndices index for each distinct vertex pb_IntArray[] sharedIndices = pb.sharedIndices; int[] sharedIndex = new int[distinctIndices.Length + points.Length]; for (int i = 0; i < distinctIndices.Length; i++) { sharedIndex[i] = sharedIndices.IndexOf(distinctIndices[i]); } for (int i = distinctIndices.Length; i < distinctIndices.Length + points.Length; i++) { sharedIndex[i] = -1; // add the new vertex to it's own sharedIndex } // Triangulate the face with the new point appended int[] tris = Delaunay.Triangulate(plane).ToIntArray(); // Check to make sure the triangulated face is facing the same direction, and flip if not Vector3 del = Vector3.Cross(verts[tris[2]] - verts[tris[0]], verts[tris[1]] - verts[tris[0]]).normalized; if (Vector3.Dot(nrm, del) > 0) { System.Array.Reverse(tris); } // Build the new face pb_Face triangulated_face = new pb_Face(tris, face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); /** * Attempt to figure out where the new UV point(s) should go (if auto uv'ed face) */ if (triangulated_face.manualUV) { for (int n = distinctIndices.Length; n < uvs.Length; n++) { // these are the two vertices that are split by the new vertex int[] adjacent_vertex = System.Array.FindAll(triangulated_face.edges, x => x.Contains(n)).Select(x => x.x != n ? x.x : x.y).ToArray(); if (adjacent_vertex.Length == 2) { uvs[n] = (uvs[adjacent_vertex[0]] + uvs[adjacent_vertex[1]]) / 2f; } else { Debug.LogWarning("Failed to find appropriate UV coordinate for new vertex point. Setting face to AutoUV."); triangulated_face.manualUV = false; } } } // Compose new face newFace = pb.AppendFace(verts, cols, uvs, triangulated_face, sharedIndex); // And delete the old pb.DeleteFace(face); return(true); }
/** * Inserts a split from each selected vertex to the center of the face */ private static bool PokeFace_Internal(pb_Object pb, pb_Face face, int[] indices_nonFaceSpecific, out Vector3 pokedVertex, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out Color[][] splitColors, out Vector2[][] splitUVs, out int[][] splitSharedIndices) { pokedVertex = Vector3.zero; splitFaces = null; splitVertices = null; splitColors = null; splitUVs = null; splitSharedIndices = null; pb_IntArray[] sharedIndices = pb.sharedIndices; ///** Sort index array such that it only uses indices local to the passed face int[] dist_indices = new int[indices_nonFaceSpecific.Length]; int[] dist_ind_si = new int[face.distinctIndices.Length]; // figure out sharedIndices index of distinct Indices for(int i = 0; i < face.distinctIndices.Length; i++) dist_ind_si[i] = sharedIndices.IndexOf(face.distinctIndices[i]); // now do the same for non-face specific indices, assigning matching groups ///** Sort index array such that it only uses indices local to the passed face for(int i = 0; i < dist_indices.Length; i++) { int ind = System.Array.IndexOf(dist_ind_si, sharedIndices.IndexOf(indices_nonFaceSpecific[i])); if(ind < 0) return false; dist_indices[i] = face.distinctIndices[ind]; } int[] indices = dist_indices.Distinct().ToArray(); // throw out splits with less than 2 vertices, or splits composed // of a single edge switch(indices.Length) { case 0: case 1: return false; case 2: if( System.Array.IndexOf(face.edges, new pb_Edge(indices[0], indices[1]) ) > -1) return false; break; default: break; } // end triangle sorting /** * The general idea here is to project the face into 2d space, * split the 2d points into groups based on the intersecting lines, * then triangulate those groups. once the groups have been * triangulated, rebuild the 3d vertices using the new groups * (building new verts for seams). * * Think like you're cutting a pie... but first the pie is a basketball, * then a pie, then a basketball again. I'm on a horse. */ Vector3[] verts = pb.GetVertices(face.distinctIndices); Vector2[] uvs = pb.GetUVs(face.distinctIndices); Color[] colors = pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices); Vector2 cenUV = pb_Bounds2D.Center(uvs); Vector3 cen3d = pb_Math.Average(verts); pokedVertex = cen3d; Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Color cenColor = pb_Math.Average(colors); // this should be cleaned up Vector2[] plane = pb_Math.PlanarProject(verts, nrm); Vector2[] indPlane = pb_Math.PlanarProject(pb.GetVertices(indices), nrm); Vector2 cen2d = pb_Math.PlanarProject( new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[indices.Length]; for(int i = 0; i < indices.Length; i++) dividers[i] = (indPlane[i] - cen2d).normalized; List<Vector2>[] quadrants2d = new List<Vector2>[indices.Length]; List<Vector3>[] quadrants3d = new List<Vector3>[indices.Length]; List<Vector2>[] quadrantsUV_2d = new List<Vector2>[indices.Length]; List<Color>[] quadrantsCol = new List<Color>[indices.Length]; List<int>[] sharedIndex = new List<int>[indices.Length]; for(int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List<Vector2>(1) { cen2d }; quadrants3d[i] = new List<Vector3>(1) { cen3d }; quadrantsUV_2d[i] = new List<Vector2>(1) { cenUV }; quadrantsCol[i] = new List<Color>(1) { cenColor }; sharedIndex[i] = new List<int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } for(int i = 0; i < face.distinctIndices.Length; i++) { // if this index is a divider, it needs to belong to the leftmost and // rightmost quadrant int indexInPokeVerts = System.Array.IndexOf(indices, face.distinctIndices[i]); int ignore = -1; if( indexInPokeVerts > -1) { // Add vert to this quadrant quadrants2d[indexInPokeVerts].Add(plane[i]); quadrants3d[indexInPokeVerts].Add(verts[i]); quadrantsUV_2d[indexInPokeVerts].Add(uvs[i]); quadrantsCol[indexInPokeVerts].Add(colors[i]); sharedIndex[indexInPokeVerts].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); // And also the one closest counter clockwise ignore = indexInPokeVerts; } Vector2 dir = (plane[i]-cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for(int j = 0; j < dividers.Length; j++) { if(j == ignore) continue; // this is a dividing vertex - ignore float dist = Vector2.Angle(dividers[j], dir); if( Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f ) dist = 360f - dist; if(dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(plane[i]); quadrants3d[quad].Add(verts[i]); quadrantsUV_2d[quad].Add(uvs[i]); quadrantsCol[quad].Add(colors[i]); sharedIndex[quad].Add(pb.sharedIndices.IndexOf(face.distinctIndices[i])); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for(int i = 0; i < len; i++) { try { tris[i] = Delaunay.Triangulate(quadrants2d[i]).ToIntArray(); if(tris[i] == null || tris[i].Length < 3) { Debug.Log("Fail triangulation"); return false; } } catch (System.Exception error) { Debug.LogError("PokeFace internal failed triangulation. Bail!\n" + error); return false; } // todo - check that face normal is correct } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitColors = new Color[len][]; splitUVs = new Vector2[len][]; splitSharedIndices = new int[len][]; for(int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); splitVertices[i] = quadrants3d[i].ToArray(); splitColors[i] = quadrantsCol[i].ToArray(); splitUVs[i] = quadrantsUV_2d[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return true; }
/** * Inserts a vertex at the center of each edge, then connects the new vertices to another new * vertex placed at the center of the face. */ private static bool SubdivideFace_Internal(pb_Object pb, pb_EdgeConnection pb_edgeConnection, out DanglingVertex?[] appendedVertices, out pb_Face[] splitFaces, out Vector3[][] splitVertices, out Color[][] splitColors, out Vector2[][] splitUVs, out int[][] splitSharedIndices) { splitFaces = null; splitVertices = null; splitColors = null; splitUVs = null; splitSharedIndices = null; appendedVertices = new DanglingVertex?[pb_edgeConnection.edges.Count]; // cache all the things pb_Face face = pb_edgeConnection.face; Dictionary<int, int> sharedIndices = pb.sharedIndices.ToDictionary(); Vector3[] vertices = pb.vertices; Vector2[] uvs = pb.uv; List<Vector2> edgeCentersUV = new List<Vector2>(); List<Vector3> edgeCenters3d = new List<Vector3>(); List<Color> edgeCentersCol = new List<Color>(); // filter duplicate edges int u = 0; List<int> usedEdgeIndices = new List<int>(); foreach(pb_Edge edge in pb_edgeConnection.edges) { int ind = face.edges.IndexOf(edge, sharedIndices); if(!usedEdgeIndices.Contains(ind)) { Vector3 cen = (vertices[edge.x] + vertices[edge.y]) / 2f; appendedVertices[u] = new DanglingVertex(cen, (pb.colors[edge.x] + pb.colors[edge.y]) / 2f); edgeCenters3d.Add(cen); edgeCentersUV.Add( (uvs[edge.x] + uvs[edge.y])/2f ); edgeCentersCol.Add( (pb.colors[edge.x] + pb.colors[edge.y])/2f ); usedEdgeIndices.Add(ind); } else { appendedVertices[u] = null; } u++; } // now we have all the vertices of the old face, plus the new edge center vertices Vector3 nrm = pb_Math.Normal(pb.GetVertices(face.indices)); Vector3[] verts3d = pb.GetVertices(face.distinctIndices); Vector2[] faceUVs = pb.GetUVs(face.distinctIndices); Color[] colors = pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices); Vector2[] verts2d = pb_Math.PlanarProject(verts3d, nrm); Vector2[] edgeCenters2d = pb_Math.PlanarProject(edgeCenters3d.ToArray(), nrm); Vector3 cen3d = pb_Math.Average(verts3d); Vector2 cenUV = pb_Bounds2D.Center(faceUVs); Vector2 cen2d = pb_Math.PlanarProject( new Vector3[1] { cen3d }, nrm)[0]; // Get the directions from which to segment this face Vector2[] dividers = new Vector2[edgeCenters2d.Length]; for(int i = 0; i < edgeCenters2d.Length; i++) dividers[i] = (edgeCenters2d[i] - cen2d).normalized; List<Vector2>[] quadrants2d = new List<Vector2>[edgeCenters2d.Length]; List<Vector3>[] quadrants3d = new List<Vector3>[edgeCenters2d.Length]; List<Vector2>[] quadrantsUV = new List<Vector2>[edgeCenters2d.Length]; List<Color>[] quadrantsCol = new List<Color>[edgeCenters2d.Length]; List<int>[] sharedIndex = new List<int>[edgeCenters2d.Length]; for(int i = 0; i < quadrants2d.Length; i++) { quadrants2d[i] = new List<Vector2>(1) { cen2d }; quadrants3d[i] = new List<Vector3>(1) { cen3d }; quadrantsUV[i] = new List<Vector2>(1) { cenUV }; quadrantsCol[i] = new List<Color>(1) { pb_Math.Average(pbUtil.ValuesWithIndices(pb.colors, face.distinctIndices)) }; sharedIndex[i] = new List<int>(1) { -2 }; // any negative value less than -1 will be treated as a new group } // add the divisors for(int i = 0; i < edgeCenters2d.Length; i++) { quadrants2d[i].Add(edgeCenters2d[i]); quadrants3d[i].Add(edgeCenters3d[i]); quadrantsUV[i].Add(edgeCentersUV[i]); quadrantsCol[i].Add(edgeCentersCol[i]); sharedIndex[i].Add(-1); // and add closest in the counterclockwise direction Vector2 dir = (edgeCenters2d[i]-cen2d).normalized; float largestClockwiseDistance = 0f; int quad = -1; for(int j = 0; j < dividers.Length; j++) { if(j == i) continue; // this is a dividing vertex - ignore float dist = Vector2.Angle(dividers[j], dir); if( Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f ) dist = 360f - dist; if(dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(edgeCenters2d[i]); quadrants3d[quad].Add(edgeCenters3d[i]); quadrantsUV[quad].Add(edgeCentersUV[i]); quadrantsCol[quad].Add(edgeCentersCol[i]); sharedIndex[quad].Add(-1); } // distribute the existing vertices for(int i = 0; i < face.distinctIndices.Length; i++) { Vector2 dir = (verts2d[i]-cen2d).normalized; // plane corresponds to distinctIndices float largestClockwiseDistance = 0f; int quad = -1; for(int j = 0; j < dividers.Length; j++) { float dist = Vector2.Angle(dividers[j], dir); if( Vector2.Dot(pb_Math.Perpendicular(dividers[j]), dir) < 0f ) dist = 360f - dist; if(dist > largestClockwiseDistance) { largestClockwiseDistance = dist; quad = j; } } quadrants2d[quad].Add(verts2d[i]); quadrants3d[quad].Add(verts3d[i]); quadrantsUV[quad].Add(faceUVs[i]); quadrantsCol[quad].Add(colors[i]); sharedIndex[quad].Add( sharedIndices[face.distinctIndices[i]] ); } int len = quadrants2d.Length; // Triangulate int[][] tris = new int[len][]; for(int i = 0; i < len; i++) { if(quadrants2d[i].Count < 3) { Debug.LogError("Insufficient points to triangulate. Exit subdivide operation. This is probably due to a concave face."); return false; } tris[i] = Delaunay.Triangulate(quadrants2d[i]).ToIntArray(); if(tris[i].Length < 3) ///< #521 return false; if( Vector3.Dot(nrm, pb_Math.Normal(quadrants3d[i][tris[i][0]], quadrants3d[i][tris[i][1]], quadrants3d[i][tris[i][2]])) < 0 ) System.Array.Reverse(tris[i]); } splitFaces = new pb_Face[len]; splitVertices = new Vector3[len][]; splitColors = new Color[len][]; splitUVs = new Vector2[len][]; splitSharedIndices = new int[len][]; for(int i = 0; i < len; i++) { // triangles, material, pb_UV, smoothing group, shared index splitFaces[i] = new pb_Face(tris[i], face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.manualUV); splitVertices[i] = quadrants3d[i].ToArray(); splitColors[i] = quadrantsCol[i].ToArray(); splitUVs[i] = quadrantsUV[i].ToArray(); splitSharedIndices[i] = sharedIndex[i].ToArray(); } return true; }