/** * 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); }
// 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); }
/** * 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); }
/** * 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 int[][] splitSharedIndices) { splitFaces = null; splitVertices = 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); Vector3 projAxis = pb_Math.GetProjectionAxis(pb_Math.Normal(pb.GetVertices(face.indices))).ToVector3(); Vector2[] plane = pb_Math.VerticesTo2DPoints(verts, projAxis); // Split points Vector3 splitPointA_3d = splitSelection.pointA; Vector3 splitPointB_3d = splitSelection.pointB; Vector2 splitPointA_2d = pb_Math.VerticesTo2DPoints(new Vector3[1] { splitPointA_3d }, projAxis)[0]; Vector2 splitPointB_2d = pb_Math.VerticesTo2DPoints(new Vector3[1] { splitPointB_3d }, projAxis)[0]; List <Vector3> v_polyA = new List <Vector3>(); // point in object space List <Vector2> v_polyA_2d = new List <Vector2>(); // point in 2d space - used to triangulate List <Vector3> v_polyB = new List <Vector3>(); // point in object space List <Vector2> v_polyB_2d = new List <Vector2>(); // point in 2d space - used to triangulate 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 == indices[i]) || (splitSelection.bIsVertex && splitSelection.indexB == indices[i])) { v_polyA.Add(verts[i]); v_polyB.Add(verts[i]); v_polyA_2d.Add(plane[i]); v_polyB_2d.Add(plane[i]); i_polyA.Add(sharedIndex[i]); i_polyB.Add(sharedIndex[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]); i_polyA.Add(sharedIndex[i]); } else { v_polyB.Add(verts[i]); v_polyB_2d.Add(plane[i]); i_polyB.Add(sharedIndex[i]); } } } if (!splitSelection.aIsVertex) { v_polyA.Add(splitPointA_3d); v_polyA_2d.Add(splitPointA_2d); v_polyB.Add(splitPointA_3d); v_polyB_2d.Add(splitPointA_2d); i_polyA.Add(-1); i_polyB.Add(-1); // neg 1 because it's a new vertex point nedgeA.Add(v_polyA.Count); nedgeB.Add(v_polyB.Count); } if (!splitSelection.bIsVertex) { v_polyA.Add(splitPointB_3d); v_polyA_2d.Add(splitPointB_2d); v_polyB.Add(splitPointB_3d); v_polyB_2d.Add(splitPointB_2d); i_polyA.Add(-1); i_polyB.Add(-1); // neg 1 because it's a new vertex point 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 = Delauney.Triangulate(v_polyA_2d).ToIntArray(); int[] t_polyB = Delauney.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.VerticesTo2DPoints(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.color); pb_Face faceB = new pb_Face(t_polyB, face.material, new pb_UV(face.uv), face.smoothingGroup, face.textureGroup, face.elementGroup, face.color); splitFaces = new pb_Face[2] { faceA, faceB }; splitVertices = new Vector3[2][] { v_polyA.ToArray(), v_polyB.ToArray() }; splitSharedIndices = new int[2][] { i_polyA.ToArray(), i_polyB.ToArray() }; return(true); }