/// <summary> /// Clear and refresh mesh in case of failure to create a shape. /// </summary> /// <param name="mesh"></param> internal static void ClearAndRefreshMesh(this ProBuilderMesh mesh) { mesh.Clear(); mesh.ToMesh(); mesh.Refresh(); }
public static void ImportMesh(ProBuilderMesh mesh) { MeshFilter filter = mesh.GetComponent <MeshFilter>(); ImportMesh(filter, mesh, Vector2.one); }
internal override MeshAndElementSelection GetElementSelection(ProBuilderMesh mesh, PivotPoint pivot, HandleOrientation orientation) { return(new MeshAndTextures(mesh, pivot, orientation)); }
/// <summary> /// Ensure that this object has a valid mesh reference, and the geometry is current. If it is not valid, this function will attempt to repair the sync state. /// </summary> /// <param name="mesh">The component to test.</param> /// <seealso cref="ProBuilderMesh.meshSyncState"/> public static void SynchronizeWithMeshFilter(ProBuilderMesh mesh) { if (mesh == null) { throw new ArgumentNullException("mesh"); } mesh.EnsureMeshFilterIsAssigned(); mesh.EnsureMeshColliderIsAssigned(); MeshSyncState state = mesh.meshSyncState; bool meshesAreAssets = Experimental.meshesAreAssets; if (state != MeshSyncState.InSync) { Mesh oldMesh; if (state == MeshSyncState.Null) { var versionID = mesh.versionID; mesh.Rebuild(); mesh.Optimize(); mesh.versionID = versionID; } else // If the mesh ID doesn't match the gameObject Id, it could mean two things: // 1. The object was just duplicated, and then made unique // 2. The scene was reloaded, and gameObject ids were recalculated. // If (2) we need to clean up the old mesh. If the (1) the old mesh needs to *not* be destroyed. if ((oldMesh = mesh.mesh) != null) { int meshNo = -1; int.TryParse(oldMesh.name.Replace("pb_Mesh", ""), out meshNo); UnityEngine.Object dup = UnityEditor.EditorUtility.InstanceIDToObject(meshNo); GameObject go = dup as GameObject; // Scene reload, just rename the mesh to the correct ID if (go == null) { mesh.mesh.name = "pb_Mesh" + mesh.id; } else { // Mesh was duplicated, need to instantiate a unique mesh asset if (!meshesAreAssets || !(IsPrefabAsset(mesh.gameObject) || IsPrefabInstance(mesh.gameObject))) { // deep copy arrays & ToMesh/Refresh mesh.MakeUnique(); mesh.Optimize(); } } } else { // old mesh didn't exist, so this is probably a prefab being instanced if (IsPrefabAsset(mesh.gameObject)) { mesh.mesh.hideFlags = (HideFlags)(1 | 2 | 4 | 8); } mesh.Optimize(); } } else { if (meshesAreAssets) { EditorMeshUtility.TryCacheMesh(mesh); } } }
/// <summary> /// Reverses the orientation of the middle edge in a quad. /// <![CDATA[ /// ``` /// . _____ _____ /// . |\ | | /| /// . | \ | => | / | /// . |____\| |/____| /// ``` /// ]]> /// /// This is the equivalent to the [Flip Face Edge](../manual/Face_FlipTri.html) action. /// </summary> /// <param name="mesh">The mesh that face belongs to.</param> /// <param name="face">The target face.</param> /// <returns>True if successful; false if not. This operation will fail if the face does not contain two triangles with exactly two shared vertices.</returns> public static bool FlipEdge(this ProBuilderMesh mesh, Face face) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (face == null) { throw new ArgumentNullException("face"); } int[] indexes = face.indexesInternal; if (indexes.Length != 6) { return(false); } int[] mode = ArrayUtility.Fill <int>(1, indexes.Length); for (int x = 0; x < indexes.Length - 1; x++) { for (int y = x + 1; y < indexes.Length; y++) { if (indexes[x] == indexes[y]) { mode[x]++; mode[y]++; } } } if (mode[0] + mode[1] + mode[2] != 5 || mode[3] + mode[4] + mode[5] != 5) { return(false); } int i0 = indexes[mode[0] == 1 ? 0 : mode[1] == 1 ? 1 : 2]; int i1 = indexes[mode[3] == 1 ? 3 : mode[4] == 1 ? 4 : 5]; int used = -1; if (mode[0] == 2) { used = indexes[0]; indexes[0] = i1; } else if (mode[1] == 2) { used = indexes[1]; indexes[1] = i1; } else if (mode[2] == 2) { used = indexes[2]; indexes[2] = i1; } if (mode[3] == 2 && indexes[3] != used) { indexes[3] = i0; } else if (mode[4] == 2 && indexes[4] != used) { indexes[4] = i0; } else if (mode[5] == 2 && indexes[5] != used) { indexes[5] = i0; } face.InvalidateCache(); return(true); }
public static List <ElementGroup> GetElementGroups(ProBuilderMesh mesh, PivotPoint pivot, HandleOrientation orientation, bool collectCoincident) { var groups = new List <ElementGroup>(); var trs = mesh.transform.localToWorldMatrix; var selectMode = ProBuilderEditor.selectMode; switch (pivot) { case PivotPoint.IndividualOrigins: { if (selectMode.ContainsFlag(SelectMode.Vertex | SelectMode.TextureVertex)) { foreach (var list in GetVertexSelectionGroups(mesh, collectCoincident)) { var bounds = Math.GetBounds(mesh.positionsInternal, list); var rot = UnityEngine.ProBuilder.HandleUtility.GetVertexRotation(mesh, orientation, list); groups.Add(new ElementGroup(list, trs.MultiplyPoint3x4(bounds.center), rot)); } } else if (selectMode.ContainsFlag(SelectMode.Edge | SelectMode.TextureEdge)) { foreach (var list in GetEdgeSelectionGroups(mesh)) { var bounds = Math.GetBounds(mesh.positionsInternal, list); var rot = UnityEngine.ProBuilder.HandleUtility.GetEdgeRotation(mesh, orientation, list); List <int> indices; if (collectCoincident) { indices = new List <int>(); mesh.GetCoincidentVertices(list, indices); } else { indices = list.SelectMany(x => new int[] { x.a, x.b }).ToList(); } groups.Add(new ElementGroup(indices, trs.MultiplyPoint3x4(bounds.center), rot)); } } else if (selectMode.ContainsFlag(SelectMode.Face | SelectMode.TextureFace)) { foreach (var list in GetFaceSelectionGroups(mesh)) { var bounds = Math.GetBounds(mesh.positionsInternal, list); var rot = UnityEngine.ProBuilder.HandleUtility.GetFaceRotation(mesh, orientation, list); List <int> indices; if (collectCoincident) { indices = new List <int>(); mesh.GetCoincidentVertices(list, indices); } else { indices = list.SelectMany(x => x.distinctIndexesInternal).ToList(); } groups.Add(new ElementGroup(indices, trs.MultiplyPoint3x4(bounds.center), rot)); } } break; } case PivotPoint.ActiveElement: { var indices = GetSelectedIndicesForSelectMode(mesh, selectMode, collectCoincident); var position = mesh.transform.position; var rotation = mesh.transform.rotation; if (selectMode.ContainsFlag(SelectMode.Face | SelectMode.TextureFace)) { var face = mesh.GetActiveFace(); if (face != null) { position = trs.MultiplyPoint3x4(Math.GetBounds(mesh.positionsInternal, face.distinctIndexesInternal).center); rotation = UnityEngine.ProBuilder.HandleUtility.GetFaceRotation(mesh, orientation, new Face[] { face }); } } else if (selectMode.ContainsFlag(SelectMode.Edge | SelectMode.TextureEdge)) { var edge = mesh.GetActiveEdge(); if (edge != Edge.Empty) { position = trs.MultiplyPoint3x4(Math.GetBounds(mesh.positionsInternal, new int [] { edge.a, edge.b }).center); rotation = UnityEngine.ProBuilder.HandleUtility.GetEdgeRotation(mesh, orientation, new Edge[] { edge }); } } else if (selectMode.ContainsFlag(SelectMode.Vertex | SelectMode.TextureVertex)) { var vertex = mesh.GetActiveVertex(); if (vertex > -1) { position = trs.MultiplyPoint3x4(mesh.positionsInternal[vertex]); rotation = UnityEngine.ProBuilder.HandleUtility.GetVertexRotation(mesh, orientation, new int[] { vertex }); } } groups.Add(new ElementGroup(indices, position, rotation)); break; } default: { var indices = GetSelectedIndicesForSelectMode(mesh, selectMode, collectCoincident); var position = MeshSelection.bounds.center; var rotation = Quaternion.identity; if (selectMode.ContainsFlag(SelectMode.Face | SelectMode.TextureFace)) { var face = mesh.GetActiveFace(); if (face != null) { rotation = UnityEngine.ProBuilder.HandleUtility.GetFaceRotation(mesh, orientation, new Face[] { face }); } } else if (selectMode.ContainsFlag(SelectMode.Edge | SelectMode.TextureEdge)) { var edge = mesh.GetActiveEdge(); if (edge != Edge.Empty) { rotation = UnityEngine.ProBuilder.HandleUtility.GetEdgeRotation(mesh, orientation, new Edge[] { edge }); } } else if (selectMode.ContainsFlag(SelectMode.Vertex | SelectMode.TextureVertex)) { var vertex = mesh.GetActiveVertex(); if (vertex > -1) { rotation = UnityEngine.ProBuilder.HandleUtility.GetVertexRotation(mesh, orientation, new int[] { vertex }); } } groups.Add(new ElementGroup(indices, position, rotation)); break; } } return(groups); }
static IEnumerable <List <Edge> > GetEdgeSelectionGroups(ProBuilderMesh mesh) { var edges = EdgeLookup.GetEdgeLookup(mesh.selectedEdgesInternal, mesh.sharedVertexLookup); var groups = new List <SimpleTuple <HashSet <int>, List <Edge> > >(); foreach (var edge in edges) { var foundMatch = false; foreach (var kvp in groups) { if (kvp.item1.Contains(edge.common.a) || kvp.item1.Contains(edge.common.b)) { kvp.item1.Add(edge.common.a); kvp.item1.Add(edge.common.b); kvp.item2.Add(edge.local); foundMatch = true; break; } } if (!foundMatch) { groups.Add(new SimpleTuple <HashSet <int>, List <Edge> >( new HashSet <int>() { edge.common.a, edge.common.b }, new List <Edge>() { edge.local })); } } // collect overlapping groups (happens in cases where selection order begins as two separate groups but // becomes one) var res = new List <List <Edge> >(); var overlap = new HashSet <int>(); for (int i = 0, c = groups.Count; i < c; i++) { if (overlap.Contains(i)) { continue; } List <Edge> grp = groups[i].item2; for (int n = i + 1; n < c; n++) { if (groups[i].item1.Overlaps(groups[n].item1)) { overlap.Add(n); grp.AddRange(groups[n].item2); } } res.Add(grp); } return(res); }
/// <summary> /// Insert a number of new points to each edge. Points are evenly spaced out along the edge. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="edges">The edges to split with points.</param> /// <param name="count">The number of new points to insert. Must be greater than 0.</param> /// <returns>The new edges created by inserting points.</returns> public static List <Edge> AppendVerticesToEdge(this ProBuilderMesh mesh, IList <Edge> edges, int count) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (edges == null) { throw new ArgumentNullException("edges"); } if (count < 1 || count > 512) { Log.Error("New edge vertex count is less than 1 or greater than 512."); return(null); } List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = mesh.sharedTextureLookup; List <int> indexesToDelete = new List <int>(); IEnumerable <Edge> commonEdges = EdgeUtility.GetSharedVertexHandleEdges(mesh, edges); List <Edge> distinctEdges = commonEdges.Distinct().ToList(); Dictionary <Face, FaceRebuildData> modifiedFaces = new Dictionary <Face, FaceRebuildData>(); int originalSharedIndexesCount = lookup.Count(); int sharedIndexesCount = originalSharedIndexesCount; foreach (Edge edge in distinctEdges) { Edge localEdge = EdgeUtility.GetEdgeWithSharedVertexHandles(mesh, edge); // Generate the new vertices that will be inserted on this edge List <Vertex> verticesToAppend = new List <Vertex>(count); for (int i = 0; i < count; i++) { verticesToAppend.Add(Vertex.Mix(vertices[localEdge.a], vertices[localEdge.b], (i + 1) / ((float)count + 1))); } List <SimpleTuple <Face, Edge> > adjacentFaces = ElementSelection.GetNeighborFaces(mesh, localEdge); // foreach face attached to common edge, append vertices foreach (SimpleTuple <Face, Edge> tup in adjacentFaces) { Face face = tup.item1; FaceRebuildData data; if (!modifiedFaces.TryGetValue(face, out data)) { data = new FaceRebuildData(); data.face = new Face(new int[0], face.submeshIndex, new AutoUnwrapSettings(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); data.vertices = new List <Vertex>(ArrayUtility.ValuesWithIndexes(vertices, face.distinctIndexesInternal)); data.sharedIndexes = new List <int>(); data.sharedIndexesUV = new List <int>(); foreach (int i in face.distinctIndexesInternal) { int shared; if (lookup.TryGetValue(i, out shared)) { data.sharedIndexes.Add(shared); } if (lookupUV.TryGetValue(i, out shared)) { data.sharedIndexesUV.Add(shared); } } indexesToDelete.AddRange(face.distinctIndexesInternal); modifiedFaces.Add(face, data); } data.vertices.AddRange(verticesToAppend); for (int i = 0; i < count; i++) { data.sharedIndexes.Add(sharedIndexesCount + i); data.sharedIndexesUV.Add(-1); } } sharedIndexesCount += count; } // now apply the changes List <Face> dic_face = modifiedFaces.Keys.ToList(); List <FaceRebuildData> dic_data = modifiedFaces.Values.ToList(); List <EdgeLookup> appendedEdges = new List <EdgeLookup>(); for (int i = 0; i < dic_face.Count; i++) { Face face = dic_face[i]; FaceRebuildData data = dic_data[i]; Vector3 nrm = Math.Normal(mesh, face); Vector2[] projection = Projection.PlanarProject(data.vertices.Select(x => x.position).ToArray(), null, nrm); int vertexCount = vertices.Count; // triangulate and set new face indexes to end of current vertex list List <int> indexes; if (Triangulation.SortAndTriangulate(projection, out indexes)) { data.face.indexesInternal = indexes.ToArray(); } else { continue; } data.face.ShiftIndexes(vertexCount); face.CopyFrom(data.face); for (int n = 0; n < data.vertices.Count; n++) { lookup.Add(vertexCount + n, data.sharedIndexes[n]); } if (data.sharedIndexesUV.Count == data.vertices.Count) { for (int n = 0; n < data.vertices.Count; n++) { lookupUV.Add(vertexCount + n, data.sharedIndexesUV[n]); } } vertices.AddRange(data.vertices); foreach (Edge e in face.edgesInternal) { EdgeLookup el = new EdgeLookup(new Edge(lookup[e.a], lookup[e.b]), e); if (el.common.a >= originalSharedIndexesCount || el.common.b >= originalSharedIndexesCount) { appendedEdges.Add(el); } } } indexesToDelete = indexesToDelete.Distinct().ToList(); int delCount = indexesToDelete.Count; var newEdges = appendedEdges.Distinct().Select(x => x.local - delCount).ToList(); mesh.SetVertices(vertices); mesh.SetSharedVertices(lookup); mesh.SetSharedTextures(lookupUV); mesh.DeleteVertices(indexesToDelete); return(newEdges); }
/// <summary> /// Iterates through face edges and builds a list using the opposite edge. /// </summary> /// <param name="pb"></param> /// <param name="edges"></param> /// <returns></returns> internal static IEnumerable <Edge> GetEdgeRing(ProBuilderMesh pb, IEnumerable <Edge> edges) { List <WingedEdge> wings = WingedEdge.GetWingedEdges(pb); List <EdgeLookup> edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList(); edgeLookup = edgeLookup.Distinct().ToList(); Dictionary <Edge, WingedEdge> wings_dic = new Dictionary <Edge, WingedEdge>(); for (int i = 0; i < wings.Count; i++) { if (!wings_dic.ContainsKey(wings[i].edge.common)) { wings_dic.Add(wings[i].edge.common, wings[i]); } } HashSet <EdgeLookup> used = new HashSet <EdgeLookup>(); for (int i = 0, c = edgeLookup.Count; i < c; i++) { WingedEdge we; if (!wings_dic.TryGetValue(edgeLookup[i].common, out we) || used.Contains(we.edge)) { continue; } WingedEdge cur = we; while (cur != null) { if (!used.Add(cur.edge)) { break; } cur = EdgeRingNext(cur); if (cur != null && cur.opposite != null) { cur = cur.opposite; } } cur = EdgeRingNext(we.opposite); if (cur != null && cur.opposite != null) { cur = cur.opposite; } // run in both directions while (cur != null) { if (!used.Add(cur.edge)) { break; } cur = EdgeRingNext(cur); if (cur != null && cur.opposite != null) { cur = cur.opposite; } } } return(used.Select(x => x.local)); }
/// <summary> /// Add a set of points to a face and retriangulate. Points are added to the nearest edge. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="face">The face to append points to.</param> /// <param name="points">Points to added to the face.</param> /// <returns>The face created by appending the points.</returns> public static Face AppendVerticesToFace(this ProBuilderMesh mesh, Face face, Vector3[] points) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (face == null) { throw new ArgumentNullException("face"); } if (points == null) { throw new ArgumentNullException("points"); } List <Vertex> vertices = mesh.GetVertices().ToList(); List <Face> faces = new List <Face>(mesh.facesInternal); Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = null; if (mesh.sharedTextures != null) { lookupUV = new Dictionary <int, int>(); SharedVertex.GetSharedVertexLookup(mesh.sharedTextures, lookupUV); } List <Edge> wound = WingedEdge.SortEdgesByAdjacency(face); List <Vertex> n_vertices = new List <Vertex>(); List <int> n_shared = new List <int>(); List <int> n_sharedUV = lookupUV != null ? new List <int>() : null; for (int i = 0; i < wound.Count; i++) { n_vertices.Add(vertices[wound[i].a]); n_shared.Add(lookup[wound[i].a]); if (lookupUV != null) { int uv; if (lookupUV.TryGetValue(wound[i].a, out uv)) { n_sharedUV.Add(uv); } else { n_sharedUV.Add(-1); } } } // now insert the new points on the nearest edge for (int i = 0; i < points.Length; i++) { int index = -1; float best = Mathf.Infinity; Vector3 p = points[i]; int vc = n_vertices.Count; for (int n = 0; n < vc; n++) { Vector3 v = n_vertices[n].position; Vector3 w = n_vertices[(n + 1) % vc].position; float dist = Math.DistancePointLineSegment(p, v, w); if (dist < best) { best = dist; index = n; } } Vertex left = n_vertices[index], right = n_vertices[(index + 1) % vc]; float x = (p - left.position).sqrMagnitude; float y = (p - right.position).sqrMagnitude; Vertex insert = Vertex.Mix(left, right, x / (x + y)); n_vertices.Insert((index + 1) % vc, insert); n_shared.Insert((index + 1) % vc, -1); if (n_sharedUV != null) { n_sharedUV.Insert((index + 1) % vc, -1); } } List <int> triangles; try { Triangulation.TriangulateVertices(n_vertices, out triangles, false); } catch { Debug.Log("Failed triangulating face after appending vertices."); return(null); } FaceRebuildData data = new FaceRebuildData(); data.face = new Face(triangles.ToArray(), face.submeshIndex, new AutoUnwrapSettings(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); data.vertices = n_vertices; data.sharedIndexes = n_shared; data.sharedIndexesUV = n_sharedUV; FaceRebuildData.Apply(new List <FaceRebuildData>() { data }, vertices, faces, lookup, lookupUV); var newFace = data.face; mesh.SetVertices(vertices); mesh.faces = faces; mesh.SetSharedVertices(lookup); mesh.SetSharedTextures(lookupUV); // check old normal and make sure this new face is pointing the same direction Vector3 oldNrm = Math.Normal(mesh, face); Vector3 newNrm = Math.Normal(mesh, newFace); if (Vector3.Dot(oldNrm, newNrm) < 0) { newFace.Reverse(); } mesh.DeleteFace(face); return(newFace); }
/// <summary> /// Insert a number of new points to an edge. Points are evenly spaced out along the edge. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="edge">The edge to split with points.</param> /// <param name="count">The number of new points to insert. Must be greater than 0.</param> /// <returns>The new edges created by inserting points.</returns> public static List <Edge> AppendVerticesToEdge(this ProBuilderMesh mesh, Edge edge, int count) { return(AppendVerticesToEdge(mesh, new Edge[] { edge }, count)); }
/// <summary> /// Insert a face between two edges. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="a">First edge.</param> /// <param name="b">Second edge</param> /// <param name="allowNonManifoldGeometry">If true, this function will allow edges to be bridged that create overlapping (non-manifold) faces.</param> /// <returns>The new face, or null of the action failed.</returns> public static Face Bridge(this ProBuilderMesh mesh, Edge a, Edge b, bool allowNonManifoldGeometry = false) { if (mesh == null) { throw new ArgumentNullException("mesh"); } SharedVertex[] sharedVertices = mesh.sharedVerticesInternal; Dictionary <int, int> lookup = mesh.sharedVertexLookup; // Check to see if a face already exists if (!allowNonManifoldGeometry) { if (ElementSelection.GetNeighborFaces(mesh, a).Count > 1 || ElementSelection.GetNeighborFaces(mesh, b).Count > 1) { return(null); } } foreach (Face face in mesh.facesInternal) { if (mesh.IndexOf(face.edgesInternal, a) >= 0 && mesh.IndexOf(face.edgesInternal, b) >= 0) { Log.Warning("Face already exists between these two edges!"); return(null); } } Vector3[] positions = mesh.positionsInternal; bool hasColors = mesh.HasArrays(MeshArrays.Color); Color[] colors = hasColors ? mesh.colorsInternal : null; Vector3[] v; Color[] c; int[] s; AutoUnwrapSettings uvs = AutoUnwrapSettings.tile; int submeshIndex = 0; // Get material and UV stuff from the first edge face SimpleTuple <Face, Edge> faceAndEdge; if (EdgeUtility.ValidateEdge(mesh, a, out faceAndEdge) || EdgeUtility.ValidateEdge(mesh, b, out faceAndEdge)) { uvs = new AutoUnwrapSettings(faceAndEdge.item1.uv); submeshIndex = faceAndEdge.item1.submeshIndex; } // Bridge will form a triangle if (a.Contains(b.a, lookup) || a.Contains(b.b, lookup)) { v = new Vector3[3]; c = new Color[3]; s = new int[3]; bool axbx = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.a)].arrayInternal, b.a) > -1; bool axby = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.a)].arrayInternal, b.b) > -1; bool aybx = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.b)].arrayInternal, b.a) > -1; bool ayby = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.b)].arrayInternal, b.b) > -1; if (axbx) { v[0] = positions[a.a]; if (hasColors) { c[0] = colors[a.a]; } s[0] = mesh.GetSharedVertexHandle(a.a); v[1] = positions[a.b]; if (hasColors) { c[1] = colors[a.b]; } s[1] = mesh.GetSharedVertexHandle(a.b); v[2] = positions[b.b]; if (hasColors) { c[2] = colors[b.b]; } s[2] = mesh.GetSharedVertexHandle(b.b); } else if (axby) { v[0] = positions[a.a]; if (hasColors) { c[0] = colors[a.a]; } s[0] = mesh.GetSharedVertexHandle(a.a); v[1] = positions[a.b]; if (hasColors) { c[1] = colors[a.b]; } s[1] = mesh.GetSharedVertexHandle(a.b); v[2] = positions[b.a]; if (hasColors) { c[2] = colors[b.a]; } s[2] = mesh.GetSharedVertexHandle(b.a); } else if (aybx) { v[0] = positions[a.b]; if (hasColors) { c[0] = colors[a.b]; } s[0] = mesh.GetSharedVertexHandle(a.b); v[1] = positions[a.a]; if (hasColors) { c[1] = colors[a.a]; } s[1] = mesh.GetSharedVertexHandle(a.a); v[2] = positions[b.b]; if (hasColors) { c[2] = colors[b.b]; } s[2] = mesh.GetSharedVertexHandle(b.b); } else if (ayby) { v[0] = positions[a.b]; if (hasColors) { c[0] = colors[a.b]; } s[0] = mesh.GetSharedVertexHandle(a.b); v[1] = positions[a.a]; if (hasColors) { c[1] = colors[a.a]; } s[1] = mesh.GetSharedVertexHandle(a.a); v[2] = positions[b.a]; if (hasColors) { c[2] = colors[b.a]; } s[2] = mesh.GetSharedVertexHandle(b.a); } return(mesh.AppendFace( v, hasColors ? c : null, new Vector2[v.Length], new Face(axbx || axby ? new int[3] { 2, 1, 0 } : new int[3] { 0, 1, 2 }, submeshIndex, uvs, 0, -1, -1, false), s)); } // Else, bridge will form a quad v = new Vector3[4]; c = new Color[4]; s = new int[4]; // shared indexes index to add to v[0] = positions[a.a]; if (hasColors) { c[0] = mesh.colorsInternal[a.a]; } s[0] = mesh.GetSharedVertexHandle(a.a); v[1] = positions[a.b]; if (hasColors) { c[1] = mesh.colorsInternal[a.b]; } s[1] = mesh.GetSharedVertexHandle(a.b); Vector3 nrm = Vector3.Cross(positions[b.a] - positions[a.a], positions[a.b] - positions[a.a]).normalized; Vector2[] planed = Projection.PlanarProject(new Vector3[4] { positions[a.a], positions[a.b], positions[b.a], positions[b.b] }, null, nrm); Vector2 ipoint = Vector2.zero; bool intersects = Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint); if (!intersects) { v[2] = positions[b.a]; if (hasColors) { c[2] = mesh.colorsInternal[b.a]; } s[2] = mesh.GetSharedVertexHandle(b.a); v[3] = positions[b.b]; if (hasColors) { c[3] = mesh.colorsInternal[b.b]; } s[3] = mesh.GetSharedVertexHandle(b.b); } else { v[2] = positions[b.b]; if (hasColors) { c[2] = mesh.colorsInternal[b.b]; } s[2] = mesh.GetSharedVertexHandle(b.b); v[3] = positions[b.a]; if (hasColors) { c[3] = mesh.colorsInternal[b.a]; } s[3] = mesh.GetSharedVertexHandle(b.a); } return(mesh.AppendFace( v, hasColors ? c : null, new Vector2[v.Length], new Face(new int[6] { 2, 1, 0, 2, 3, 1 }, submeshIndex, uvs, 0, -1, -1, false), s)); }
/// <summary> /// Rebuild a mesh from an ordered set of points. /// </summary> /// <param name="mesh">The target mesh. The mesh values will be cleared and repopulated with the shape extruded from points.</param> /// <param name="points">A path of points to triangulate and extrude.</param> /// <param name="extrude">The distance to extrude.</param> /// <param name="flipNormals">If true the faces will be inverted at creation.</param> /// <param name="holePoints">Holes in the polygon. If null this will be ignored.</param> /// <returns>An ActionResult with the status of the operation.</returns> public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList <Vector3> points, float extrude, bool flipNormals, IList <IList <Vector3> > holePoints) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (points == null || points.Count < 3) { ClearAndRefreshMesh(mesh); return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points")); } Vector3[] vertices = points.ToArray(); Vector3[][] holeVertices = null; if (holePoints != null && holePoints.Count > 0) { holeVertices = new Vector3[holePoints.Count][]; for (int i = 0; i < holePoints.Count; i++) { if (holePoints[i] == null || holePoints[i].Count < 3) { ClearAndRefreshMesh(mesh); return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points in hole " + i)); } holeVertices[i] = holePoints[i].ToArray(); } } List <int> triangles; Log.PushLogLevel(LogLevel.Error); if (Triangulation.TriangulateVertices(vertices, out triangles, holeVertices)) { Vector3[] combinedVertices = null; if (holeVertices != null) { combinedVertices = new Vector3[vertices.Length + holeVertices.Sum(arr => arr.Length)]; Array.Copy(vertices, combinedVertices, vertices.Length); int destinationIndex = vertices.Length; foreach (var hole in holeVertices) { Array.ConstrainedCopy(hole, 0, combinedVertices, destinationIndex, hole.Length); destinationIndex += hole.Length; } } else { combinedVertices = vertices; } int[] indexes = triangles.ToArray(); if (Math.PolygonArea(combinedVertices, indexes) < Mathf.Epsilon) { ClearAndRefreshMesh(mesh); Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Failure, "Polygon Area < Epsilon")); } mesh.Clear(); mesh.positionsInternal = combinedVertices; var newFace = new Face(indexes); mesh.facesInternal = new[] { newFace }; mesh.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(combinedVertices); mesh.InvalidateCaches(); // check that all points are represented in the triangulation if (newFace.distinctIndexesInternal.Length != combinedVertices.Length) { ClearAndRefreshMesh(mesh); Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Failure, "Triangulation missing points")); } Vector3 nrm = Math.Normal(mesh, mesh.facesInternal[0]); nrm = mesh.gameObject.transform.TransformDirection(nrm); if ((flipNormals ? Vector3.Dot(mesh.gameObject.transform.up, nrm) > 0f : Vector3.Dot(mesh.gameObject.transform.up, nrm) < 0f)) { mesh.facesInternal[0].Reverse(); } if (extrude != 0.0f) { mesh.DuplicateAndFlip(mesh.facesInternal); mesh.Extrude(new Face[] { (flipNormals ? mesh.facesInternal[1] : mesh.facesInternal[0]) }, ExtrudeMethod.IndividualFaces, extrude); if ((extrude < 0f && !flipNormals) || (extrude > 0f && flipNormals)) { foreach (var face in mesh.facesInternal) { face.Reverse(); } } } mesh.ToMesh(); mesh.Refresh(); } else { // clear mesh instead of showing an invalid one ClearAndRefreshMesh(mesh); Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Failure, "Failed Triangulating Points")); } Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Success, "Create Polygon Shape")); }
public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList <Vector3> points, float extrude, bool flipNormals, Vector3 cameraLookAt, IList <IList <Vector3> > holePoints = null) { return(CreateShapeFromPolygon(mesh, points, extrude, flipNormals, null)); }
static string ExportPrefab(string path, ProBuilderMesh pb, bool replaceOriginal, bool batchExport) { string name = Path.GetFileNameWithoutExtension(path); string basePath = AssetPathFromFullPath(path, null); pb.ToMesh(); pb.Refresh(); pb.Optimize(); string meshPath = string.Format("{0}.asset", basePath); string prefabPath = string.Format("{0}.prefab", basePath); if (batchExport) { // Never overwrite during batch export. meshPath = AssetDatabase.GenerateUniqueAssetPath(meshPath); prefabPath = AssetDatabase.GenerateUniqueAssetPath(prefabPath); } else { var existingMesh = AssetDatabase.LoadAssetAtPath <Mesh>(meshPath); if (existingMesh) { // Overwriting mesh. var existingPrefab = AssetDatabase.LoadAssetAtPath <GameObject>(prefabPath); if (existingPrefab) { // Overwriting prefab as well. var meshFilter = existingPrefab.GetComponent <MeshFilter>(); if (!meshFilter || meshFilter.sharedMesh != existingMesh) { // Prefab and mesh being overwritten are not related, pick different path. meshPath = PickDifferentAssetPath(meshPath); } // Else allow overwrite both as they are related. } else { // Unrelated mesh is being overriden, pick different path. meshPath = PickDifferentAssetPath(meshPath); } } } Mesh meshAsset = pb.mesh; meshAsset.name = name; meshAsset = CreateOrReplaceAsset(meshAsset, meshPath); var go = replaceOriginal ? pb.gameObject : Object.Instantiate(pb.gameObject); var component = go.GetComponent <ProBuilderMesh>(); Undo.RecordObject(component, "Export ProBuilderMesh as Replacement"); StripProBuilderScripts.DestroyProBuilderMeshAndDependencies(go, component, true, true); go.GetComponent <MeshFilter>().sharedMesh = meshAsset; var meshCollider = go.GetComponent <MeshCollider>(); if (meshCollider) { meshCollider.sharedMesh = meshAsset; } if (replaceOriginal) { PrefabUtility.SaveAsPrefabAssetAndConnect(go, prefabPath, InteractionMode.UserAction); } else { // if we're about to overwrite the prefab asset of the source mesh, first disconnect it so that we're not // overwriting the instance if (PrefabUtility.IsPartOfAnyPrefab(pb) && PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(pb) == prefabPath) { PrefabUtility.UnpackPrefabInstance(pb.gameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); } PrefabUtility.SaveAsPrefabAsset(go, prefabPath); } if (!replaceOriginal) { pb.mesh = null; EditorUtility.SynchronizeWithMeshFilter(pb); Object.DestroyImmediate(go); } return(meshPath); }
void OnGUI() { DoContextMenu(); GUILayout.BeginHorizontal(EditorStyles.toolbar); EditorGUI.BeginChangeCheck(); s_ShowSettings.value = GUILayout.Toggle(s_ShowSettings.value, g_Settings, EditorStyles.toolbarButton); s_ShowPreview.value = GUILayout.Toggle(s_ShowPreview.value, g_Preview, EditorStyles.toolbarButton); s_ShowNormals.value = GUILayout.Toggle(s_ShowNormals.value, g_Normals, EditorStyles.toolbarButton); if (s_ShowNormals) { EditorGUI.BeginChangeCheck(); s_NormalsSize.value = GUILayout.HorizontalSlider( s_NormalsSize, .001f, 1f, GUILayout.MinWidth(30f), GUILayout.MaxWidth(100f)); if (EditorGUI.EndChangeCheck()) { foreach (var kvp in m_SmoothGroups) { kvp.Value.RebuildNormalsMesh(kvp.Key); } } } if (EditorGUI.EndChangeCheck()) { ProBuilderSettings.Save(); SceneView.RepaintAll(); } GUILayout.FlexibleSpace(); if (GUILayout.Button(m_HelpIcon, UI.EditorStyles.toolbarHelpIcon)) { s_ShowHelp.SetValue(!s_ShowHelp, true); } GUILayout.EndHorizontal(); if (s_ShowSettings) { GUILayout.BeginVertical(UI.EditorStyles.settingsGroup); EditorGUIUtility.labelWidth = 100; EditorGUI.BeginChangeCheck(); s_PreviewOpacity.value = EditorGUILayout.Slider(g_PreviewOpacity, s_PreviewOpacity, .001f, 1f); s_PreviewDither.value = EditorGUILayout.Toggle(g_PreviewDither, s_PreviewDither); if (EditorGUI.EndChangeCheck()) { ProBuilderSettings.Save(); smoothPreviewMaterial.SetFloat("_Opacity", s_PreviewOpacity); smoothPreviewMaterial.SetFloat("_Dither", s_PreviewDither ? 1f : 0f); SceneView.RepaintAll(); } EditorGUIUtility.labelWidth = 0; GUILayout.EndVertical(); } m_Scroll = EditorGUILayout.BeginScrollView(m_Scroll); if (s_ShowHelp) { GUILayout.BeginVertical(UI.EditorStyles.settingsGroup); GUILayout.Label("Create and Clear Smoothing Groups", EditorStyles.boldLabel); GUILayout.Label("Adjacent faces with the same smoothing group will appear to have a soft adjoining edge.", wordWrappedRichText); GUILayout.Space(2); GUILayout.Label("<b>To smooth</b> a selected group of faces, click one of the Smooth Group buttons.", wordWrappedRichText); GUILayout.Label("<b>To clear</b> selected faces of their smooth group, click the [Break] icon.", wordWrappedRichText); GUILayout.Label("<b>To select</b> all faces in a group, Right+Click or Alt+Click a smooth group button.", wordWrappedRichText); GUILayout.Space(2); UI.EditorGUILayout.BeginRow(); GUILayout.Button("1", groupButtonStyle); GUILayout.Label("An unused smooth group", wordWrappedRichText); UI.EditorGUILayout.EndRow(); UI.EditorGUILayout.BeginRow(); GUILayout.Button("1", groupButtonInUseStyle); GUILayout.Label("A smooth group that is in use, but not in the current selection", wordWrappedRichText); UI.EditorGUILayout.EndRow(); UI.EditorGUILayout.BeginRow(); GUILayout.Button("1", groupButtonSelectedStyle); GUILayout.Label("A smooth group that is currently selected", wordWrappedRichText); UI.EditorGUILayout.EndRow(); UI.EditorGUILayout.BeginRow(); GUILayout.Button("1", groupButtonMixedSelectionStyle); GUI.backgroundColor = Color.white; GUILayout.Label("A smooth group is selected, but the selection also contains non-grouped faces", wordWrappedRichText); UI.EditorGUILayout.EndRow(); if (GUILayout.Button("Open Documentation")) { Application.OpenURL("https://docs.unity3d.com/Packages/com.unity.probuilder@latest/index.html?subfolder=/manual/workflow-edit-smoothing.html"); } GUILayout.EndVertical(); } // border style is 4 margin, 4 pad, 1px content. inner is accounted for by btn size + btn margin. float area = (position.width - 10); float margin = Mathf.Max(groupButtonStyle.margin.left, groupButtonStyle.margin.right); int columns = (int)(area / (groupButtonStyle.CalcSize(m_GroupKeyContent).x + margin)) - 1; if (m_SmoothGroups.Count < 1) { GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); GUILayout.Label("Select a ProBuilder Mesh", UI.EditorGUIUtility.CenteredGreyMiniLabel); GUILayout.FlexibleSpace(); GUILayout.EndVertical(); } else { foreach (var mesh in m_SmoothGroups) { ProBuilderMesh pb = mesh.Key; SmoothGroupData data = mesh.Value; GUILayout.BeginVertical(UI.EditorStyles.settingsGroup); GUILayout.BeginHorizontal(); if (GUILayout.Button(pb.name, UI.EditorStyles.headerLabel)) { data.isVisible = !data.isVisible; } GUILayout.FlexibleSpace(); if (GUILayout.Button(m_SelectFacesWithSmoothGroupSelectionContent, UI.EditorStyles.buttonStyle)) { SelectGroups(pb, new HashSet <int>(pb.selectedFacesInternal.Select(x => x.smoothingGroup))); } if (GUILayout.Button(m_BreakSmoothingContent, UI.EditorStyles.buttonStyle)) { SetGroup(pb, Smoothing.smoothingGroupNone); } GUILayout.EndHorizontal(); bool isMixedSelection = data.selected.Contains(Smoothing.smoothingGroupNone); if (data.isVisible) { int column = 0; bool anySmoothGroups = data.groups.Any(x => x.Key > Smoothing.smoothingGroupNone); GUILayout.BeginHorizontal(); for (int i = 1; i < Smoothing.smoothRangeMax; i++) { bool isSelected = data.selected.Contains(i); GUIStyle stateStyle = isSelected ? (isMixedSelection ? groupButtonMixedSelectionStyle : groupButtonSelectedStyle) : data.groups.ContainsKey(i) ? groupButtonInUseStyle : groupButtonStyle; if (s_ShowPreview && anySmoothGroups) { GUILayout.BeginVertical(GUILayout.MaxWidth(IconWidth)); } m_GroupKeyContent.text = i.ToString(); if (GUILayout.Button(m_GroupKeyContent, stateStyle)) { // if right click or alt click select the faces instead of setting a group if ((Event.current.modifiers & EventModifiers.Alt) == EventModifiers.Alt || Event.current.button != 0) { SelectGroups(pb, new HashSet <int>() { i }); } else { SetGroup(pb, i); } } if (s_ShowPreview && anySmoothGroups) { GUI.backgroundColor = data.groupColors.ContainsKey(i) ? data.groupColors[i] : Color.clear; GUILayout.Label("", colorKeyStyle); GUILayout.EndVertical(); GUI.backgroundColor = Color.white; } if (++column > columns) { column = 0; GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); } } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); } } EditorGUILayout.EndScrollView(); }
public override MeshSelection Select(Camera camera, Rect rect, Rect uiRootRect, GameObject[] gameObjects, bool depthTest, MeshEditorSelectionMode mode) { MeshSelection selection = new MeshSelection(); m_faceSelection.BeginChange(); Dictionary <ProBuilderMesh, HashSet <Face> > result = PBUtility.PickFaces(camera, rect, uiRootRect, gameObjects, depthTest); if (mode == MeshEditorSelectionMode.Add) { foreach (KeyValuePair <ProBuilderMesh, HashSet <Face> > kvp in result) { ProBuilderMesh mesh = kvp.Key; IList <Face> faces = mesh.faces; IList <int> faceIndices = kvp.Value.Select(f => faces.IndexOf(f)).ToArray(); IList <int> notSelected = faceIndices.Where(f => !m_faceSelection.IsSelected(mesh, f)).ToArray(); foreach (int face in notSelected) { m_faceSelection.Add(mesh, face); } selection.SelectedFaces.Add(mesh, notSelected); } } else if (mode == MeshEditorSelectionMode.Substract) { foreach (KeyValuePair <ProBuilderMesh, HashSet <Face> > kvp in result) { ProBuilderMesh mesh = kvp.Key; IList <Face> faces = mesh.faces; IList <int> faceIndices = kvp.Value.Select(f => faces.IndexOf(f)).ToArray(); IList <int> selected = faceIndices.Where(f => m_faceSelection.IsSelected(mesh, f)).ToArray(); foreach (int face in selected) { m_faceSelection.Remove(mesh, face); } selection.UnselectedFaces.Add(mesh, selected); } } else if (mode == MeshEditorSelectionMode.Difference) { foreach (KeyValuePair <ProBuilderMesh, HashSet <Face> > kvp in result) { ProBuilderMesh mesh = kvp.Key; IList <Face> faces = mesh.faces; IList <int> faceIndices = kvp.Value.Select(f => faces.IndexOf(f)).ToArray(); IList <int> selected = faceIndices.Where(f => m_faceSelection.IsSelected(mesh, f)).ToArray(); IList <int> notSelected = faceIndices.Where(f => !m_faceSelection.IsSelected(mesh, f)).ToArray(); foreach (int face in selected) { m_faceSelection.Remove(mesh, face); } foreach (int face in notSelected) { m_faceSelection.Add(mesh, face); } selection.UnselectedFaces.Add(mesh, selected); selection.SelectedFaces.Add(mesh, notSelected); } } m_faceSelection.EndChange(); if (selection.SelectedFaces.Count == 0 && selection.UnselectedFaces.Count == 0) { selection = null; } return(selection); }
static void ElementSelectionChanged(ProBuilderMesh mesh) { InvalidateElementSelection(); }
public MeshAndElementSelection(ProBuilderMesh mesh, PivotPoint pivot, HandleOrientation orientation, bool collectCoincidentIndices) { m_Mesh = mesh; m_ElementGroups = ElementGroup.GetElementGroups(mesh, pivot, orientation, collectCoincidentIndices); }
internal static bool Contains(ProBuilderMesh mesh) { return(s_TopSelection.Contains(mesh)); }
internal static bool IsPrefab(ProBuilderMesh mesh) { return(PrefabUtility.GetPrefabAssetType(mesh.gameObject) != PrefabAssetType.NotAPrefab); }
/// <summary> /// move the UVs to where the edges passed meet /// </summary> /// <param name="mesh"></param> /// <param name="faceToMove"></param> /// <param name="edgeToAlignTo"></param> /// <param name="edgeToBeAligned"></param> /// <param name="channel"></param> /// <returns></returns> static bool AlignEdges(ProBuilderMesh mesh, Face faceToMove, Edge edgeToAlignTo, Edge edgeToBeAligned, int channel) { Vector2[] uvs = GetUVs(mesh, channel); SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal; // Match each edge vertex to the other int[] matchX = new int[2] { edgeToAlignTo.a, -1 }; int[] matchY = new int[2] { edgeToAlignTo.b, -1 }; int siIndex = mesh.GetSharedVertexHandle(edgeToAlignTo.a); if (siIndex < 0) { return(false); } if (sharedIndexes[siIndex].Contains(edgeToBeAligned.a)) { matchX[1] = edgeToBeAligned.a; matchY[1] = edgeToBeAligned.b; } else { matchX[1] = edgeToBeAligned.b; matchY[1] = edgeToBeAligned.a; } // scale face 2 to match the edge size of f1 float dist_e1 = Vector2.Distance(uvs[edgeToAlignTo.a], uvs[edgeToAlignTo.b]); float dist_e2 = Vector2.Distance(uvs[edgeToBeAligned.a], uvs[edgeToBeAligned.b]); float scale = dist_e1 / dist_e2; // doesn't matter what point we scale around because we'll move it in the next step anyways foreach (int i in faceToMove.distinctIndexesInternal) { uvs[i] = uvs[i].ScaleAroundPoint(Vector2.zero, Vector2.one * scale); } /** * Figure out where the center of each edge is so that we can move the f2 edge to match f1's origin */ Vector2 f1_center = (uvs[edgeToAlignTo.a] + uvs[edgeToAlignTo.b]) / 2f; Vector2 f2_center = (uvs[edgeToBeAligned.a] + uvs[edgeToBeAligned.b]) / 2f; Vector2 diff = f1_center - f2_center; /** * Move f2 face to where it's matching edge center is on top of f1's center */ foreach (int i in faceToMove.distinctIndexesInternal) { uvs[i] += diff; } /** * Now that the edge's centers are matching, rotate f2 to match f1's angle */ Vector2 angle1 = uvs[matchY[0]] - uvs[matchX[0]]; Vector2 angle2 = uvs[matchY[1]] - uvs[matchX[1]]; float angle = Vector2.Angle(angle1, angle2); if (Vector3.Cross(angle1, angle2).z < 0) { angle = 360f - angle; } foreach (int i in faceToMove.distinctIndexesInternal) { uvs[i] = Math.RotateAroundPoint(uvs[i], f1_center, angle); } float error = Mathf.Abs(Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]])) + Mathf.Abs(Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]])); // now check that the matched UVs are on top of one another if the error allowance is greater than some small value if (error > .02f) { // first try rotating 180 degrees foreach (int i in faceToMove.distinctIndexesInternal) { uvs[i] = Math.RotateAroundPoint(uvs[i], f1_center, 180f); } float e2 = Mathf.Abs(Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]])) + Mathf.Abs(Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]])); if (e2 < error) { error = e2; } else { // flip 'em back around foreach (int i in faceToMove.distinctIndexesInternal) { uvs[i] = Math.RotateAroundPoint(uvs[i], f1_center, 180f); } } } // If successfully aligned, merge the sharedIndexesUV SplitUVs(mesh, faceToMove.distinctIndexesInternal); mesh.SetTexturesCoincident(matchX); mesh.SetTexturesCoincident(matchY); ApplyUVs(mesh, uvs, channel); return(true); }
internal override MeshAndElementSelection GetElementSelection(ProBuilderMesh mesh, PivotPoint pivot) { return(new TranslateTextureSelection(mesh, pivot)); }
/// <inheritdoc/> public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation) { var upDir = Vector3.Scale(rotation * Vector3.up, size); var rightDir = Vector3.Scale(rotation * Vector3.right, size); var forwardDir = Vector3.Scale(rotation * Vector3.forward, size); var xRadius = rightDir.magnitude / 2f; var yRadius = upDir.magnitude; var depth = forwardDir.magnitude / 2f; var radialCuts = m_NumberOfSides + 1; var angle = m_ArchDegrees; var templateOut = new Vector2[radialCuts]; var templateIn = new Vector2[radialCuts]; if (angle < 90f) { xRadius *= 2f; } else if (angle < 180f) { xRadius *= 1f + Mathf.Lerp(1f, 0f, Mathf.Abs(Mathf.Cos(angle * Mathf.Deg2Rad))); } else if (angle > 180f) { yRadius /= 1f + Mathf.Lerp(0f, 1f, (angle - 180f) / 90f); } for (int i = 0; i < radialCuts; i++) { var currentAngle = i * (angle / (radialCuts - 1)); Vector2 tangent; templateOut[i] = Math.PointInEllipseCircumference(xRadius, yRadius, currentAngle, Vector2.zero, out tangent); templateIn[i] = Math.PointInEllipseCircumference(xRadius - m_Thickness, yRadius - m_Thickness, currentAngle, Vector2.zero, out tangent); } List <Vector3> v = new List <Vector3>(); Vector2 tmp, tmp2, tmp3, tmp4; float y = -depth; int smoothedFaceCount = 0; for (int n = 0; n < radialCuts - 1; n++) { // outside faces tmp = templateOut[n]; tmp2 = n < (radialCuts - 1) ? templateOut[n + 1] : templateOut[n]; Vector3[] qvo = GetFace(tmp, tmp2, -depth); // inside faces tmp = templateIn[n]; tmp2 = n < (radialCuts - 1) ? templateIn[n + 1] : templateIn[n]; Vector3[] qvi = GetFace(tmp2, tmp, -depth); // left side bottom face if (angle < 360f && m_EndCaps) { if (n == 0) { v.AddRange(GetFace(templateOut[n], templateIn[n], depth)); } } v.AddRange(qvo); v.AddRange(qvi); smoothedFaceCount += 2; if (angle < 360f && m_EndCaps) { // right side bottom face if (n == radialCuts - 2) { v.AddRange(GetFace(templateIn[n + 1], templateOut[n + 1], depth)); } } } // build front and back faces for (int i = 0; i < radialCuts - 1; i++) { tmp = templateOut[i]; tmp2 = (i < radialCuts - 1) ? templateOut[i + 1] : templateOut[i]; tmp3 = templateIn[i]; tmp4 = (i < radialCuts - 1) ? templateIn[i + 1] : templateIn[i]; // front Vector3[] tpb = new Vector3[4] { new Vector3(tmp.x, tmp.y, depth), new Vector3(tmp2.x, tmp2.y, depth), new Vector3(tmp3.x, tmp3.y, depth), new Vector3(tmp4.x, tmp4.y, depth), }; // back Vector3[] tpt = new Vector3[4] { new Vector3(tmp2.x, tmp2.y, y), new Vector3(tmp.x, tmp.y, y), new Vector3(tmp4.x, tmp4.y, y), new Vector3(tmp3.x, tmp3.y, y) }; v.AddRange(tpb); v.AddRange(tpt); } var sizeSigns = Math.Sign(size); for (int i = 0; i < v.Count; i++) { v[i] = Vector3.Scale(rotation * v[i], sizeSigns); } mesh.GeometryWithPoints(v.ToArray()); if (m_Smooth) { for (int i = (angle < 360f && m_EndCaps) ? 1 : 0; i < smoothedFaceCount; i++) { mesh.facesInternal[i].smoothingGroup = 1; } } var sizeSign = sizeSigns.x * sizeSigns.y * sizeSigns.z; if (sizeSign < 0) { var faces = mesh.facesInternal; foreach (var face in faces) { face.Reverse(); } } mesh.TranslateVerticesInWorldSpace(mesh.mesh.triangles, mesh.transform.TransformDirection(-mesh.mesh.bounds.center)); mesh.Refresh(); return(UpdateBounds(mesh, size, rotation, new Bounds())); }
/// <summary> /// Returns the [winding order](../manual/gloss.html#winding) for a face. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="face">The face to test.</param> /// <returns>The winding order if successful; <see cref="WindingOrder.Unknown"/> if not.</returns> public static WindingOrder GetWindingOrder(this ProBuilderMesh mesh, Face face) { Vector2[] p = Projection.PlanarProject(mesh.positionsInternal, face.distinctIndexesInternal); return(GetWindingOrder(p)); }
public abstract Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation);
private static void ImportMesh(ProBuilderMesh mesh, Vector2 uvScale) { MeshFilter filter = mesh.GetComponent <MeshFilter>(); ImportMesh(filter, mesh, uvScale); }
public virtual Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds) { return(mesh.mesh.bounds); }
/// <summary> /// Merge a collection of <see cref="ProBuilderMesh"/> objects to as few meshes as possible. It will re-use the meshTarget object as the first /// destination for the first <see cref="ProBuilderMesh.maxVertexCount"/> -1 vertices. If the sum of vertices is above <see cref="ProBuilderMesh.maxVertexCount"/> - 1 /// it will generate new meshes unless there is a single mesh left in which case it will append it to the return list. /// </summary> /// <param name="meshes">A collection of meshes to be merged. Note: it is expected that meshes includes meshTarget.</param> /// <param name="meshTarget">A mesh which will be used as the starting point for merging and which will be kept as reference/target.</param> /// <returns> /// A list of merged meshes. In most cases this will be a single mesh corresponding to meshTarget. However it can be multiple in cases /// where the resulting vertex count exceeds the maximum allowable value. /// </returns> public static List <ProBuilderMesh> Combine(IEnumerable <ProBuilderMesh> meshes, ProBuilderMesh meshTarget) { if (meshes == null) { throw new ArgumentNullException("meshes"); } if (meshTarget == null) { throw new ArgumentNullException("meshTarget"); } if (!meshes.Any() || meshes.Count() < 2) { return(null); } if (!meshes.Contains(meshTarget)) { return(null); } var vertices = new List <Vertex>(meshTarget.GetVertices()); var faces = new List <Face>(meshTarget.facesInternal); var sharedVertices = new List <SharedVertex>(meshTarget.sharedVertices); var sharedTextures = new List <SharedVertex>(meshTarget.sharedTextures); int offset = meshTarget.vertexCount; var materialMap = new List <Material>(meshTarget.renderer.sharedMaterials); var targetTransform = meshTarget.transform; var firstMeshContributors = new List <ProBuilderMesh>(); var remainderMeshContributors = new List <ProBuilderMesh>(); var currentMeshVertexCount = offset; foreach (var mesh in meshes) { if (mesh != meshTarget) { if (currentMeshVertexCount + mesh.vertexCount < ProBuilderMesh.maxVertexCount) { currentMeshVertexCount += mesh.vertexCount; firstMeshContributors.Add(mesh); } else { remainderMeshContributors.Add(mesh); } } } var autoUvFaces = new List <Face>(); AccumulateMeshesInfo( firstMeshContributors, offset, ref vertices, ref faces, ref autoUvFaces, ref sharedVertices, ref sharedTextures, ref materialMap, targetTransform ); meshTarget.SetVertices(vertices); meshTarget.faces = faces; meshTarget.sharedVertices = sharedVertices; meshTarget.sharedVertices = sharedTextures != null?sharedTextures.ToArray() : null; meshTarget.renderer.sharedMaterials = materialMap.ToArray(); meshTarget.ToMesh(); meshTarget.Refresh(); UVEditing.SetAutoAndAlignUnwrapParamsToUVs(meshTarget, autoUvFaces); var returnedMesh = new List <ProBuilderMesh>() { meshTarget }; if (remainderMeshContributors.Count > 1) { var newMeshes = CombineToNewMeshes(remainderMeshContributors); foreach (var mesh in newMeshes) { returnedMesh.Add(mesh); } } else if (remainderMeshContributors.Count == 1) { returnedMesh.Add(remainderMeshContributors[0]); } return(returnedMesh); }
/// <summary> /// Append a new face to the ProBuilderMesh. /// </summary> /// <param name="mesh">The mesh target.</param> /// <param name="positions">The new vertex positions to add.</param> /// <param name="colors">The new colors to add (must match positions length).</param> /// <param name="uvs">The new uvs to add (must match positions length).</param> /// <param name="face">A face with the new triangle indexes. The indexes should be 0 indexed.</param> /// <param name="common"></param> /// <returns>The new face as referenced on the mesh.</returns> internal static Face AppendFace(this ProBuilderMesh mesh, Vector3[] positions, Color[] colors, Vector2[] uvs, Face face, int[] common) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (positions == null) { throw new ArgumentNullException("positions"); } if (face == null) { throw new ArgumentNullException("face"); } int faceVertexCount = positions.Length; if (common == null) { common = new int[faceVertexCount]; for (int i = 0; i < faceVertexCount; i++) { common[i] = -1; } } int vertexCount = mesh.vertexCount; var mc = mesh.HasArrays(MeshArrays.Color); var fc = colors != null; var mt = mesh.HasArrays(MeshArrays.Texture0); var ft = uvs != null; Vector3[] newPositions = new Vector3[vertexCount + faceVertexCount]; Color[] newColors = (mc || fc) ? new Color[vertexCount + faceVertexCount] : null; Vector2[] newTextures = (mt || ft) ? new Vector2[vertexCount + faceVertexCount] : null; List <Face> faces = new List <Face>(mesh.facesInternal); Array.Copy(mesh.positionsInternal, 0, newPositions, 0, vertexCount); Array.Copy(positions, 0, newPositions, vertexCount, faceVertexCount); if (mc || fc) { Array.Copy(mc ? mesh.colorsInternal : ArrayUtility.Fill(Color.white, vertexCount), 0, newColors, 0, vertexCount); Array.Copy(fc ? colors : ArrayUtility.Fill(Color.white, faceVertexCount), 0, newColors, vertexCount, colors.Length); } if (mt || ft) { Array.Copy(mt ? mesh.texturesInternal : ArrayUtility.Fill(Vector2.zero, vertexCount), 0, newTextures, 0, vertexCount); Array.Copy(ft ? uvs : ArrayUtility.Fill(Vector2.zero, faceVertexCount), 0, newTextures, mesh.texturesInternal.Length, faceVertexCount); } face.ShiftIndexesToZero(); face.ShiftIndexes(vertexCount); faces.Add(face); for (int i = 0; i < common.Length; i++) { if (common[i] < 0) { mesh.AddSharedVertex(new SharedVertex(new int[] { i + vertexCount })); } else { mesh.AddToSharedVertex(common[i], i + vertexCount); } } mesh.positions = newPositions; mesh.colors = newColors; mesh.textures = newTextures; mesh.faces = faces; return(face); }