// 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; }
/** * Sets the passed faces to use Auto or Manual UVs, and (if previously manual) splits any vertex connections. */ public static void SetAutoUV(pb_Object pb, pb_Face[] faces, bool auto) { if (auto) { faces = System.Array.FindAll(faces, x => x.manualUV).ToArray(); // only operate on faces that were previously manual pb.SplitUVs(pb_Face.AllTriangles(faces)); Vector2[][] uv_origins = new Vector2[faces.Length][]; for (int i = 0; i < faces.Length; i++) { uv_origins[i] = pb.GetUVs(faces[i].distinctIndices); } for (int f = 0; f < faces.Length; f++) { faces[f].uv.Reset(); faces[f].manualUV = !auto; faces[f].elementGroup = -1; } pb.RefreshUV(faces); for (int i = 0; i < faces.Length; i++) { pb_Transform2D transform = MatchCoordinates(pb.GetUVs(faces[i].distinctIndices), uv_origins[i]); faces[i].uv.offset = -transform.position; faces[i].uv.rotation = transform.rotation; if (Mathf.Abs(transform.scale.sqrMagnitude - 2f) > .1f) { faces[i].uv.scale = transform.scale; } } } else { foreach (pb_Face f in faces) { f.textureGroup = -1; f.manualUV = !auto; } } }
/** * 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); }
/** * 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); }
/** * 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 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; }
/** * 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); }
/** * Sets the passed faces to use Auto or Manual UVs, and (if previously manual) splits any vertex connections. */ public static void SetAutoUV(pb_Object pb, pb_Face[] faces, bool auto) { if(auto) { faces = System.Array.FindAll(faces, x => x.manualUV).ToArray(); // only operate on faces that were previously manual pb.SplitUVs( pb_Face.AllTriangles(faces) ); Vector2[][] uv_origins = new Vector2[faces.Length][]; for(int i = 0; i < faces.Length; i++) uv_origins[i] = pb.GetUVs(faces[i].distinctIndices); for(int f = 0; f < faces.Length; f++) { faces[f].uv.Reset(); faces[f].manualUV = !auto; faces[f].elementGroup = -1; } pb.RefreshUV(faces); for(int i = 0; i < faces.Length; i++) { pb_Transform2D transform = MatchCoordinates(pb.GetUVs(faces[i].distinctIndices), uv_origins[i]); faces[i].uv.offset = -transform.position; faces[i].uv.rotation = transform.rotation; if( Mathf.Abs(transform.scale.sqrMagnitude - 2f) > .1f ) faces[i].uv.scale = transform.scale; } } else { foreach(pb_Face f in faces) { f.textureGroup = -1; f.manualUV = !auto; } } }