/// <summary> /// Iterates through face edges and builds a list using the opposite edge, iteratively. /// </summary> /// <param name="pb">The probuilder mesh</param> /// <param name="edges">The edges already selected</param> /// <returns>The new selected edges</returns> internal static IEnumerable <Edge> GetEdgeRingIterative(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)) { continue; } WingedEdge cur = we; if (!used.Contains(cur.edge)) { used.Add(cur.edge); } var next = EdgeRingNext(cur); if (next != null && next.opposite != null && !used.Contains(next.edge)) { used.Add(next.edge); } var prev = EdgeRingNext(cur.opposite); if (prev != null && prev.opposite != null && !used.Contains(prev.edge)) { used.Add(prev.edge); } } return(used.Select(x => x.local)); }
//private void RemoveParallelIncomingEdges(Node<T, TActivity> node) //{ // if (node is null) // { // throw new ArgumentNullException(nameof(node)); // } // // Clean up any dummy edges that are parallel coming into the head node. // if (node.NodeType == NodeType.Start || node.NodeType == NodeType.Isolated) // { // return; // } // // First, find the tail nodes that connect to this node via ALL edges. // // In a vertex graph, all edges should be removable. // var tailNodeParallelEdgesLookup = new Dictionary<T, HashSet<T>>(); // IEnumerable<T> removableIncomingEdgeIds = // node.IncomingEdges.Select(x => EdgeLookup[x]) // .Where(x => x.Content.CanBeRemoved) // .Select(x => x.Id); // foreach (T incomingEdgeId in removableIncomingEdgeIds) // { // T tailNodeId = EdgeTailNodeLookup[incomingEdgeId].Id; // if (!tailNodeParallelEdgesLookup.TryGetValue(tailNodeId, out HashSet<T> edgeIds)) // { // edgeIds = new HashSet<T>(); // tailNodeParallelEdgesLookup.Add(tailNodeId, edgeIds); // } // if (!edgeIds.Contains(incomingEdgeId)) // { // edgeIds.Add(incomingEdgeId); // } // } // // Now find the tail nodes that connect to this node via multiple edges. // IList<T> setsOfMoreThanOneEdge = // tailNodeParallelEdgesLookup // .Where(x => x.Value.Count > 1) // .Select(x => x.Key) // .ToList(); // foreach (T tailNodeId in setsOfMoreThanOneEdge) // { // Node<T, TActivity> tailNode = EdgeTailNodeLookup[tailNodeId]; // IList<T> edgeIds = tailNodeParallelEdgesLookup[tailNodeId].ToList(); // int length = edgeIds.Count; // // Leave one edge behind. // for (int i = 1; i < length; i++) // { // T edgeId = edgeIds[i]; // // Remove the edge from the tail node. // tailNode.OutgoingEdges.Remove(edgeId); // EdgeTailNodeLookup.Remove(edgeId); // // Remove the edge from the head node. // node.IncomingEdges.Remove(edgeId); // EdgeHeadNodeLookup.Remove(edgeId); // // Remove the edge completely. // EdgeLookup.Remove(edgeId); // } // } //} private void RemoveRedundantIncomingEdges(T nodeId, IDictionary <T, HashSet <T> > nodeIdAncestorLookup) { if (nodeIdAncestorLookup is null) { throw new ArgumentNullException(nameof(nodeIdAncestorLookup)); } Node <T, TActivity> node = NodeLookup[nodeId]; if (node.NodeType == NodeType.Start || node.NodeType == NodeType.Isolated) { return; } // Go through all the incoming edges and collate the // ancestors of their tail nodes. var tailNodeAncestors = new HashSet <T>(node.IncomingEdges .Select(x => EdgeTailNodeLookup[x].Id) .SelectMany(x => nodeIdAncestorLookup[x])); // Go through the incoming edges and remove any that connect // directly to any ancestors of the edges' tail nodes. // In a vertex graph, all edges should be removable. foreach (T edgeId in node.IncomingEdges.Select(x => EdgeLookup[x]).Where(x => x.Content.CanBeRemoved).Select(x => x.Id).ToList()) { Node <T, TActivity> tailNode = EdgeTailNodeLookup[edgeId]; T edgeTailNodeId = tailNode.Id; if (tailNodeAncestors.Contains(edgeTailNodeId)) { // Remove the edge from the tail node. tailNode.OutgoingEdges.Remove(edgeId); EdgeTailNodeLookup.Remove(edgeId); // Remove the edge from the node itself. node.IncomingEdges.Remove(edgeId); EdgeHeadNodeLookup.Remove(edgeId); // Remove the edge completely. EdgeLookup.Remove(edgeId); } } // Go through all the remaining incoming edges and repeat. foreach (T tailNodeId in node.IncomingEdges.Select(x => EdgeTailNodeLookup[x].Id).ToList()) { RemoveRedundantIncomingEdges(tailNodeId, nodeIdAncestorLookup); } }
public void Analyze(MinutiaPairing pairing, EdgeLookup lookup, Template probe, Template candidate) { MaxDistanceError = lookup.MaxDistanceError; MaxAngleError = lookup.MaxAngleError; var innerDistanceRadius = Convert.ToInt32(DistanceErrorFlatness * MaxDistanceError); var innerAngleRadius = Convert.ToInt32(AngleErrorFlatness * MaxAngleError); PairCount = pairing.Count; EdgeCount = 0; SupportedCount = 0; CorrectTypeCount = 0; DistanceErrorSum = 0; AngleErrorSum = 0; for (int i = 0; i < PairCount; ++i) { PairInfo pair = pairing.GetPair(i); if (pair.SupportingEdges >= MinSupportingEdges) { ++SupportedCount; } EdgeCount += pair.SupportingEdges + 1; if (probe.Minutiae[pair.Pair.Probe].Type == candidate.Minutiae[pair.Pair.Candidate].Type) { ++CorrectTypeCount; } if (i > 0) { var probeEdge = EdgeConstructor.Construct(probe, pair.Reference.Probe, pair.Pair.Probe); var candidateEdge = EdgeConstructor.Construct(candidate, pair.Reference.Candidate, pair.Pair.Candidate); DistanceErrorSum += Math.Abs(probeEdge.Length - candidateEdge.Length); AngleErrorSum += Math.Max(innerDistanceRadius, Angle.Distance(probeEdge.ReferenceAngle, candidateEdge.ReferenceAngle)); AngleErrorSum += Math.Max(innerAngleRadius, Angle.Distance(probeEdge.NeighborAngle, candidateEdge.NeighborAngle)); } } float probeFraction = PairCount / (float)probe.Minutiae.Length; float candidateFraction = PairCount / (float)candidate.Minutiae.Length; PairFraction = (probeFraction + candidateFraction) / 2; }
/// <summary> /// Attempts to find edges along an Edge loop in an iterative way /// /// Adds two edges to the selection, one at each extremity /// </summary> /// <param name="mesh"></param> /// <param name="lastEdgesAdded"></param> /// <param name="loop"></param> /// <returns></returns> internal static bool GetEdgeLoopIterative(ProBuilderMesh mesh, IEnumerable <Edge> edges, out Edge[] loop) { List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh); IEnumerable <EdgeLookup> m_edgeLookup = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup); HashSet <EdgeLookup> sources = new HashSet <EdgeLookup>(m_edgeLookup); HashSet <EdgeLookup> used = new HashSet <EdgeLookup>(); for (int i = 0; i < wings.Count; i++) { if (!sources.Contains(wings[i].edge)) { continue; } GetEdgeLoopInternalIterative(wings[i], wings[i].edge.common, used); } loop = used.Select(x => x.local).ToArray(); return(true); }
protected override void AnythingChanged() { if (mesh == null) { return; } var edges = viewState == MeshViewState.All ? mesh.faces.SelectMany(x => x.edgesInternal) : mesh.selectedEdgesInternal; var common = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup); foreach (var edge in common) { if (m_Content.ContainsKey(edge)) { m_Content[edge] += ", " + edge.local; } else { m_Content.Add(edge, string.Format("<b>{0}</b>: {1}", edge.common, edge.local)); } } }
public void PickEdges_DepthTestOff_RectSelectComplete() { Setup(); var edges = TestEdgePick(new PickerOptions() { depthTest = false, rectSelectMode = RectSelectMode.Complete }); Assert.IsNotNull(edges, "Selection is null"); var selection = edges.FirstOrDefault(); Assert.IsNotNull(selection, "Selection is null"); HashSet <Edge> selectedElements = selection.Value; Assert.Greater(selectedElements.Count, 0); Dictionary <int, int> commonLookup = selection.Key.sharedVertexLookup; var allEdges = EdgeLookup.GetEdgeLookupHashSet(selection.Key.facesInternal.SelectMany(x => x.edgesInternal), commonLookup); var selectedEdges = EdgeLookup.GetEdgeLookupHashSet(selectedElements, commonLookup); Assert.AreEqual(allEdges.Count, selectedEdges.Count); Cleanup(); }
public static void DoMouseDrag(Rect mouseDragRect, SelectMode selectionMode, ScenePickerPreferences scenePickerPreferences) { var pickingOptions = new PickerOptions() { depthTest = scenePickerPreferences.cullMode == CullingMode.Back, rectSelectMode = scenePickerPreferences.rectSelectMode }; UndoUtility.RecordSelection("Drag Select"); bool isAppendModifier = EditorHandleUtility.IsAppendModifier(Event.current.modifiers); if (!isAppendModifier) { MeshSelection.ClearElementSelection(); } bool elementsInDragRect = false; switch (selectionMode) { case SelectMode.Vertex: case SelectMode.TextureVertex: { Dictionary <ProBuilderMesh, HashSet <int> > selected = SelectionPicker.PickVerticesInRect( SceneView.lastActiveSceneView.camera, mouseDragRect, MeshSelection.topInternal, pickingOptions, EditorGUIUtility.pixelsPerPoint); foreach (var kvp in selected) { var mesh = kvp.Key; SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal; HashSet <int> common; if (isAppendModifier) { common = mesh.GetSharedVertexHandles(mesh.selectedIndexesInternal); if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Add) { common.UnionWith(kvp.Value); } else if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Subtract) { common.RemoveWhere(x => kvp.Value.Contains(x)); } else if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Difference) { common.SymmetricExceptWith(kvp.Value); } } else { common = kvp.Value; } elementsInDragRect |= kvp.Value.Any(); mesh.SetSelectedVertices(common.SelectMany(x => sharedIndexes[x])); } break; } case SelectMode.Face: case SelectMode.TextureFace: { Dictionary <ProBuilderMesh, HashSet <Face> > selected = SelectionPicker.PickFacesInRect( SceneView.lastActiveSceneView.camera, mouseDragRect, MeshSelection.topInternal, pickingOptions, EditorGUIUtility.pixelsPerPoint); foreach (var kvp in selected) { HashSet <Face> current; if (isAppendModifier) { current = new HashSet <Face>(kvp.Key.selectedFacesInternal); if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Add) { current.UnionWith(kvp.Value); } else if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Subtract) { current.RemoveWhere(x => kvp.Value.Contains(x)); } else if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Difference) { current.SymmetricExceptWith(kvp.Value); } } else { current = kvp.Value; } elementsInDragRect |= kvp.Value.Any(); kvp.Key.SetSelectedFaces(current); } break; } case SelectMode.Edge: case SelectMode.TextureEdge: { var selected = SelectionPicker.PickEdgesInRect( SceneView.lastActiveSceneView.camera, mouseDragRect, MeshSelection.topInternal, pickingOptions, EditorGUIUtility.pixelsPerPoint); foreach (var kvp in selected) { ProBuilderMesh mesh = kvp.Key; Dictionary <int, int> common = mesh.sharedVertexLookup; HashSet <EdgeLookup> selectedEdges = EdgeLookup.GetEdgeLookupHashSet(kvp.Value, common); HashSet <EdgeLookup> current; if (isAppendModifier) { current = EdgeLookup.GetEdgeLookupHashSet(mesh.selectedEdges, common); if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Add) { current.UnionWith(selectedEdges); } else if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Subtract) { current.RemoveWhere(x => selectedEdges.Contains(x)); } else if (scenePickerPreferences.selectionModifierBehavior == SelectionModifierBehavior.Difference) { current.SymmetricExceptWith(selectedEdges); } } else { current = selectedEdges; } elementsInDragRect |= kvp.Value.Any(); mesh.SetSelectedEdges(current.Select(x => x.local)); } break; } } // if nothing was selected in the drag rect, clear the object selection too if (!elementsInDragRect && !isAppendModifier) { MeshSelection.ClearElementAndObjectSelection(); } ProBuilderEditor.Refresh(); SceneView.RepaintAll(); }
/// <summary> /// Inserts new edges connecting the passed edges, optionally restricting new edge insertion to faces in faceMask. /// </summary> /// <param name="mesh"></param> /// <param name="edges"></param> /// <param name="addedFaces"></param> /// <param name="connections"></param> /// <param name="returnFaces"></param> /// <param name="returnEdges"></param> /// <param name="faceMask"></param> /// <returns></returns> internal static ActionResult Connect( this ProBuilderMesh mesh, IEnumerable <Edge> edges, out Face[] addedFaces, out Edge[] connections, bool returnFaces = false, bool returnEdges = false, HashSet <Face> faceMask = null) { Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = mesh.sharedTextureLookup; HashSet <EdgeLookup> distinctEdges = new HashSet <EdgeLookup>(EdgeLookup.GetEdgeLookup(edges, lookup)); List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh); // map each edge to a face so that we have a list of all touched faces with their to-be-subdivided edges Dictionary <Face, List <WingedEdge> > touched = new Dictionary <Face, List <WingedEdge> >(); foreach (WingedEdge wing in wings) { if (distinctEdges.Contains(wing.edge)) { List <WingedEdge> faceEdges; if (touched.TryGetValue(wing.face, out faceEdges)) { faceEdges.Add(wing); } else { touched.Add(wing.face, new List <WingedEdge>() { wing }); } } } Dictionary <Face, List <WingedEdge> > affected = new Dictionary <Face, List <WingedEdge> >(); // weed out edges that won't actually connect to other edges (if you don't play ya' can't stay) foreach (KeyValuePair <Face, List <WingedEdge> > kvp in touched) { if (kvp.Value.Count <= 1) { WingedEdge opp = kvp.Value[0].opposite; if (opp == null) { continue; } List <WingedEdge> opp_list; if (!touched.TryGetValue(opp.face, out opp_list)) { continue; } if (opp_list.Count <= 1) { continue; } } affected.Add(kvp.Key, kvp.Value); } List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); List <ConnectFaceRebuildData> results = new List <ConnectFaceRebuildData>(); // just the faces that where connected with > 1 edge List <Face> connectedFaces = new List <Face>(); HashSet <int> usedTextureGroups = new HashSet <int>(mesh.facesInternal.Select(x => x.textureGroup)); int newTextureGroupIndex = 1; // do the splits foreach (KeyValuePair <Face, List <WingedEdge> > split in affected) { Face face = split.Key; List <WingedEdge> targetEdges = split.Value; int inserts = targetEdges.Count; Vector3 nrm = Math.Normal(vertices, face.indexesInternal); if (inserts == 1 || (faceMask != null && !faceMask.Contains(face))) { ConnectFaceRebuildData c; if (InsertVertices(face, targetEdges, vertices, out c)) { Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal); if (Vector3.Dot(nrm, fn) < 0) { c.faceRebuildData.face.Reverse(); } results.Add(c); } } else if (inserts > 1) { List <ConnectFaceRebuildData> res = inserts == 2 ? ConnectEdgesInFace(face, targetEdges[0], targetEdges[1], vertices) : ConnectEdgesInFace(face, targetEdges, vertices); if (face.textureGroup < 0) { while (usedTextureGroups.Contains(newTextureGroupIndex)) { newTextureGroupIndex++; } usedTextureGroups.Add(newTextureGroupIndex); } if (res == null) { connections = null; addedFaces = null; return(new ActionResult(ActionResult.Status.Failure, "Unable to connect faces")); } else { foreach (ConnectFaceRebuildData c in res) { connectedFaces.Add(c.faceRebuildData.face); Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal); if (Vector3.Dot(nrm, fn) < 0) { c.faceRebuildData.face.Reverse(); } c.faceRebuildData.face.textureGroup = face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup; c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv); c.faceRebuildData.face.submeshIndex = face.submeshIndex; c.faceRebuildData.face.smoothingGroup = face.smoothingGroup; c.faceRebuildData.face.manualUV = face.manualUV; } results.AddRange(res); } } } FaceRebuildData.Apply(results.Select(x => x.faceRebuildData), mesh, vertices, null); mesh.sharedTextures = new SharedVertex[0]; int removedVertexCount = mesh.DeleteFaces(affected.Keys).Length; mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal); mesh.ToMesh(); // figure out where the new edges where inserted if (returnEdges) { // offset the newVertexIndexes by whatever the FaceRebuildData did so we can search for the new edges by index var appended = new HashSet <int>(); for (int n = 0; n < results.Count; n++) { for (int i = 0; i < results[n].newVertexIndexes.Count; i++) { appended.Add((results[n].newVertexIndexes[i] + results[n].faceRebuildData.Offset()) - removedVertexCount); } } Dictionary <int, int> lup = mesh.sharedVertexLookup; IEnumerable <Edge> newEdges = results.SelectMany(x => x.faceRebuildData.face.edgesInternal).Where(x => appended.Contains(x.a) && appended.Contains(x.b)); IEnumerable <EdgeLookup> distNewEdges = EdgeLookup.GetEdgeLookup(newEdges, lup); connections = distNewEdges.Distinct().Select(x => x.local).ToArray(); } else { connections = null; } if (returnFaces) { addedFaces = connectedFaces.ToArray(); } else { addedFaces = null; } return(new ActionResult(ActionResult.Status.Success, string.Format("Connected {0} Edges", results.Count / 2))); }
/// <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> /// Extrude faces as groups. /// </summary> /// <param name="mesh"></param> /// <param name="faces"></param> /// <param name="compensateAngleVertexDistance"></param> /// <param name="distance"></param> /// <returns></returns> static Face[] ExtrudeAsGroups(ProBuilderMesh mesh, IEnumerable <Face> faces, bool compensateAngleVertexDistance, float distance) { if (faces == null || !faces.Any()) { return(null); } List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); int sharedIndexMax = mesh.sharedVerticesInternal.Length; int sharedIndexOffset = 0; Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = mesh.sharedTextureLookup; List <Face> newFaces = new List <Face>(); // old triangle index -> old shared index Dictionary <int, int> oldSharedMap = new Dictionary <int, int>(); // old shared index -> new shared index Dictionary <int, int> newSharedMap = new Dictionary <int, int>(); // bridge face extruded edges, maps vertex index to new extruded vertex position Dictionary <int, int> delayPosition = new Dictionary <int, int>(); // used to average the direction of vertices shared by perimeter edges // key[shared index], value[normal count, normal sum] Dictionary <int, SimpleTuple <Vector3, Vector3, List <int> > > extrudeMap = new Dictionary <int, SimpleTuple <Vector3, Vector3, List <int> > >(); List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces, true); List <HashSet <Face> > groups = GetFaceGroups(wings); foreach (HashSet <Face> group in groups) { Dictionary <EdgeLookup, Face> perimeter = GetPerimeterEdges(group, lookup); newSharedMap.Clear(); oldSharedMap.Clear(); foreach (var edgeAndFace in perimeter) { EdgeLookup edge = edgeAndFace.Key; Face face = edgeAndFace.Value; int vc = vertices.Count; int x = edge.local.a, y = edge.local.b; if (!oldSharedMap.ContainsKey(x)) { oldSharedMap.Add(x, lookup[x]); int newSharedIndex = -1; if (newSharedMap.TryGetValue(lookup[x], out newSharedIndex)) { lookup[x] = newSharedIndex; } else { newSharedIndex = sharedIndexMax + (sharedIndexOffset++); newSharedMap.Add(lookup[x], newSharedIndex); lookup[x] = newSharedIndex; } } if (!oldSharedMap.ContainsKey(y)) { oldSharedMap.Add(y, lookup[y]); int newSharedIndex = -1; if (newSharedMap.TryGetValue(lookup[y], out newSharedIndex)) { lookup[y] = newSharedIndex; } else { newSharedIndex = sharedIndexMax + (sharedIndexOffset++); newSharedMap.Add(lookup[y], newSharedIndex); lookup[y] = newSharedIndex; } } lookup.Add(vc + 0, oldSharedMap[x]); lookup.Add(vc + 1, oldSharedMap[y]); lookup.Add(vc + 2, lookup[x]); lookup.Add(vc + 3, lookup[y]); delayPosition.Add(vc + 2, x); delayPosition.Add(vc + 3, y); vertices.Add(new Vertex(vertices[x])); vertices.Add(new Vertex(vertices[y])); // extruded edge will be positioned later vertices.Add(null); vertices.Add(null); Face bridge = new Face( new int[6] { vc + 0, vc + 1, vc + 2, vc + 1, vc + 3, vc + 2 }, face.submeshIndex, new AutoUnwrapSettings(face.uv), Smoothing.smoothingGroupNone, -1, -1, false ); newFaces.Add(bridge); } foreach (Face face in group) { // @todo keep together if possible face.textureGroup = -1; Vector3 normal = Math.Normal(mesh, face); for (int i = 0; i < face.distinctIndexesInternal.Length; i++) { int idx = face.distinctIndexesInternal[i]; // If this vertex is on the perimeter but not part of a perimeter edge // move the sharedIndex to match it's new value. if (!oldSharedMap.ContainsKey(idx) && newSharedMap.ContainsKey(lookup[idx])) { lookup[idx] = newSharedMap[lookup[idx]]; } int com = lookup[idx]; // Break any UV shared connections if (lookupUV != null && lookupUV.ContainsKey(face.distinctIndexesInternal[i])) { lookupUV.Remove(face.distinctIndexesInternal[i]); } // add the normal to the list of normals for this shared vertex SimpleTuple <Vector3, Vector3, List <int> > dir; if (extrudeMap.TryGetValue(com, out dir)) { dir.item1 += normal; dir.item3.Add(idx); extrudeMap[com] = dir; } else { extrudeMap.Add(com, new SimpleTuple <Vector3, Vector3, List <int> >(normal, normal, new List <int>() { idx })); } } } } foreach (var kvp in extrudeMap) { Vector3 direction = (kvp.Value.item1 / kvp.Value.item3.Count); direction.Normalize(); // If extruding by face normal extend vertices on seams by the hypotenuse float modifier = compensateAngleVertexDistance ? Math.Secant(Vector3.Angle(direction, kvp.Value.item2) * Mathf.Deg2Rad) : 1f; direction.x *= distance * modifier; direction.y *= distance * modifier; direction.z *= distance * modifier; foreach (int i in kvp.Value.item3) { vertices[i].position += direction; } } foreach (var kvp in delayPosition) { vertices[kvp.Key] = new Vertex(vertices[kvp.Value]); } mesh.SetVertices(vertices); var fc = mesh.faceCount; var nc = newFaces.Count; var appended = new Face[fc + nc]; Array.Copy(mesh.facesInternal, 0, appended, 0, fc); for (int i = fc, c = fc + nc; i < c; i++) { appended[i] = newFaces[i - fc]; } mesh.faces = appended; mesh.SetSharedVertices(lookup); mesh.SetSharedTextures(lookupUV); return(newFaces.ToArray()); }
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); }
public override bool RemoveActivityDependencies(T activityId, HashSet <T> dependencies) { if (dependencies is null) { throw new ArgumentNullException(nameof(dependencies)); } if (!NodeLookup.TryGetValue(activityId, out Node <T, TActivity> node)) { return(false); } if (!dependencies.Any()) { return(true); } RemoveUnsatisfiedSuccessorActivityDependencies(activityId, dependencies); if (node.NodeType == NodeType.Start || node.NodeType == NodeType.Isolated) { return(true); } // If any dependencies currently exist, remove them. var existingDependencyLookup = new HashSet <T>(NodeLookup.Keys.Intersect(dependencies)); IList <T> incomingEdgeIds = node.IncomingEdges.ToList(); int length = incomingEdgeIds.Count; for (int i = 0; i < length; i++) { T edgeId = incomingEdgeIds[i]; Node <T, TActivity> tailNode = EdgeTailNodeLookup[edgeId]; if (!existingDependencyLookup.Contains(tailNode.Id)) { continue; } // Remove the edge from the tail node. tailNode.OutgoingEdges.Remove(edgeId); EdgeTailNodeLookup.Remove(edgeId); if (!tailNode.OutgoingEdges.Any()) { if (tailNode.NodeType == NodeType.Normal) { tailNode.SetNodeType(NodeType.End); } else if (tailNode.NodeType == NodeType.Start) { tailNode.SetNodeType(NodeType.Isolated); } } // Remove the edge from the head node. node.IncomingEdges.Remove(edgeId); EdgeHeadNodeLookup.Remove(edgeId); // Remove the edge completely. EdgeLookup.Remove(edgeId); } if (!node.IncomingEdges.Any()) { if (node.NodeType == NodeType.Normal) { node.SetNodeType(NodeType.Start); } else if (node.NodeType == NodeType.End) { node.SetNodeType(NodeType.Isolated); } } return(true); }
public override bool AddActivityDependencies(T activityId, HashSet <T> dependencies) { if (dependencies is null) { throw new ArgumentNullException(nameof(dependencies)); } if (!NodeLookup.TryGetValue(activityId, out Node <T, TActivity> node)) { return(false); } if (!dependencies.Any()) { return(true); } if (dependencies.Contains(activityId)) { return(false); } // If the node is an Start or Isolated node, then convert it. if (node.NodeType == NodeType.Start) { node.SetNodeType(NodeType.Normal); } else if (node.NodeType == NodeType.Isolated) { node.SetNodeType(NodeType.End); } // Check which of the expected dependencies currently exist. IList <T> existingDependencies = NodeLookup.Keys.Intersect(dependencies).ToList(); IList <T> nonExistingDependencies = dependencies.Except(existingDependencies).ToList(); // If any expected dependencies currently exist, generate an edge to connect them. foreach (T dependencyId in existingDependencies) { Node <T, TActivity> dependencyNode = NodeLookup[dependencyId]; T edgeId = EdgeIdGenerator(); var edge = new Edge <T, TEvent>(EventGenerator(edgeId)); node.IncomingEdges.Add(edgeId); EdgeHeadNodeLookup.Add(edgeId, node); // If the dependency node is an End or Isolated node, then convert it. if (dependencyNode.NodeType == NodeType.End) { dependencyNode.SetNodeType(NodeType.Normal); } else if (dependencyNode.NodeType == NodeType.Isolated) { dependencyNode.SetNodeType(NodeType.Start); } dependencyNode.OutgoingEdges.Add(edgeId); EdgeTailNodeLookup.Add(edgeId, dependencyNode); EdgeLookup.Add(edgeId, edge); } // If any expected dependencies currently do not exist, then record their // IDs and add this node as an unsatisfied successor. foreach (T dependencyId in nonExistingDependencies) { if (!UnsatisfiedSuccessorsLookup.TryGetValue(dependencyId, out HashSet <Node <T, TActivity> > successorNodes)) { successorNodes = new HashSet <Node <T, TActivity> >(); UnsatisfiedSuccessorsLookup.Add(dependencyId, successorNodes); } successorNodes.Add(node); } return(true); }
/// <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)); }
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>(); var j = -1; var groupIdx = -1; 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); } } j++; // Make sure the last selected edge is the last in the group var idx = grp.IndexOf(mesh.selectedEdgesInternal[mesh.selectedEdgesInternal.Length - 1]); if (idx != -1 && idx < grp.Count) { var item = grp[idx]; groupIdx = j; grp[idx] = grp[grp.Count - 1]; grp[grp.Count - 1] = item; } res.Add(grp); } // Make sure the last selected edge's group is the last in the groups if (groupIdx != -1 && groupIdx < res.Count) { var item = res[groupIdx]; res[groupIdx] = res[res.Count - 1]; res[res.Count - 1] = item; } return(res); }
public bool RemoveDummyActivity(T activityId) { // Retrieve the activity's edge. if (!EdgeLookup.TryGetValue(activityId, out Edge <T, TActivity> edge)) { return(false); } if (!edge.Content.IsDummy) { return(false); } if (!edge.Content.CanBeRemoved) { return(false); } Node <T, TEvent> tailNode = EdgeTailNodeLookup[activityId]; Node <T, TEvent> headNode = EdgeHeadNodeLookup[activityId]; // Check to make sure that no other edges will be made parallel // by removing this edge. if (HaveDecendantOrAncestorOverlap(tailNode, headNode) && !ShareMoreThanOneEdge(tailNode, headNode)) { return(false); } // Remove the edge from the tail node. tailNode.OutgoingEdges.Remove(activityId); EdgeTailNodeLookup.Remove(activityId); // Remove the edge from the head node. headNode.IncomingEdges.Remove(activityId); EdgeHeadNodeLookup.Remove(activityId); // Remove the edge completely. EdgeLookup.Remove(activityId); // If the head node is not the End node, and it has no more incoming // edges, then transfer the head node's outgoing edges to the tail node. if (headNode.NodeType != NodeType.End && headNode.NodeType != NodeType.Isolated && !headNode.IncomingEdges.Any()) { IList <T> headNodeOutgoingEdgeIds = headNode.OutgoingEdges.ToList(); foreach (T headNodeOutgoingEdgeId in headNodeOutgoingEdgeIds) { bool changeTailSuccess = ChangeEdgeTailNode(headNodeOutgoingEdgeId, tailNode.Id); if (!changeTailSuccess) { throw new InvalidOperationException($@"Unable to change tail node of edge {headNodeOutgoingEdgeId} to node {tailNode.Id} when removing dummy activity {activityId}"); } } } else if (tailNode.NodeType != NodeType.Start && tailNode.NodeType != NodeType.Isolated && !tailNode.OutgoingEdges.Any()) { // If the tail node is not the Start node, and it has no more outgoing // edges, then transfer the tail node's incoming edges to the head node. IList <T> tailNodeIncomingEdgeIds = tailNode.IncomingEdges.ToList(); foreach (T tailNodeIncomingEdgeId in tailNodeIncomingEdgeIds) { bool changeHeadSuccess = ChangeEdgeHeadNode(tailNodeIncomingEdgeId, headNode.Id); if (!changeHeadSuccess) { throw new InvalidOperationException($@"Unable to change head node of edge {tailNodeIncomingEdgeId} to node {headNode.Id} when removing dummy activity {activityId}"); } } } return(true); }
public override bool AddActivity(TActivity activity, HashSet <T> dependencies) { if (activity == null) { throw new ArgumentNullException(nameof(activity)); } if (dependencies is null) { throw new ArgumentNullException(nameof(dependencies)); } if (EdgeLookup.ContainsKey(activity.Id)) { return(false); } if (dependencies.Contains(activity.Id)) { return(false); } // Create a new edge for the activity. var edge = new Edge <T, TActivity>(activity); EdgeLookup.Add(edge.Id, edge); // We expect dependencies at some point. if (dependencies.Any()) { // Since we use dummy edges to connect all tail nodes, we can create // a new tail node for this edge. T tailEventId = NodeIdGenerator(); var tailNode = new Node <T, TEvent>(EventGenerator(tailEventId)); tailNode.OutgoingEdges.Add(edge.Id); EdgeTailNodeLookup.Add(edge.Id, tailNode); NodeLookup.Add(tailNode.Id, tailNode); // Check which of the expected dependencies currently exist. IList <T> existingDependencies = EdgeLookup.Keys.Intersect(dependencies).ToList(); IList <T> nonExistingDependencies = dependencies.Except(existingDependencies).ToList(); // If any expected dependencies currently exist, then hook up their head // node to this edge's tail node with dummy edges. foreach (T dependencyId in existingDependencies) { Node <T, TEvent> dependencyHeadNode = EdgeHeadNodeLookup[dependencyId]; T dummyEdgeId = EdgeIdGenerator(); var dummyEdge = new Edge <T, TActivity>(DummyActivityGenerator(dummyEdgeId)); tailNode.IncomingEdges.Add(dummyEdgeId); EdgeHeadNodeLookup.Add(dummyEdgeId, tailNode); // If the head node of the dependency is the End node, then convert it. if (dependencyHeadNode.NodeType == NodeType.End) { dependencyHeadNode.SetNodeType(NodeType.Normal); } dependencyHeadNode.OutgoingEdges.Add(dummyEdgeId); EdgeTailNodeLookup.Add(dummyEdgeId, dependencyHeadNode); EdgeLookup.Add(dummyEdgeId, dummyEdge); } // If any expected dependencies currently do not exist, then record their // IDs and add this edge's tail node as an unsatisfied successor. foreach (T dependencyId in nonExistingDependencies) { if (!UnsatisfiedSuccessorsLookup.TryGetValue(dependencyId, out HashSet <Node <T, TEvent> > tailNodes)) { tailNodes = new HashSet <Node <T, TEvent> >(); UnsatisfiedSuccessorsLookup.Add(dependencyId, tailNodes); } tailNodes.Add(tailNode); } } else { // No dependencies, so attach it directly to the start node. StartNode.OutgoingEdges.Add(edge.Id); EdgeTailNodeLookup.Add(edge.Id, StartNode); } ResolveUnsatisfiedSuccessorActivities(edge.Id); return(true); }
/// <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); Edge edgeLookUp = new Edge(lookup[localEdge.a], lookup[localEdge.b]); Edge e = new Edge(); // 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); //Ordering vertices in the new face List <Vertex> orderedVertices = new List <Vertex>(); List <int> orderedSharedIndexes = new List <int>(); List <int> orderedSharedUVIndexes = new List <int>(); List <Edge> peripheralEdges = WingedEdge.SortEdgesByAdjacency(face); for (int i = 0; i < peripheralEdges.Count; i++) { e.a = peripheralEdges[i].a; e.b = peripheralEdges[i].b; orderedVertices.Add(vertices[e.a]); int shared; if (lookup.TryGetValue(e.a, out shared)) { orderedSharedIndexes.Add(shared); } if (lookupUV.TryGetValue(i, out shared)) { data.sharedIndexesUV.Add(shared); } if (edgeLookUp.a == lookup[e.a] && edgeLookUp.b == lookup[e.b]) { for (int j = 0; j < count; j++) { orderedVertices.Add(verticesToAppend[j]); orderedSharedIndexes.Add(sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } else if (edgeLookUp.a == lookup[e.b] && edgeLookUp.b == lookup[e.a]) { for (int j = count - 1; j >= 0; j--) { orderedVertices.Add(verticesToAppend[j]); orderedSharedIndexes.Add(sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } } data.vertices = orderedVertices; data.sharedIndexes = orderedSharedIndexes; data.sharedIndexesUV = orderedSharedUVIndexes; } else { //Get ordered vertices in the existing face and add new ones List <Vertex> orderedVertices = data.vertices; List <int> orderedSharedIndexes = data.sharedIndexes; List <int> orderedSharedUVIndexes = data.sharedIndexesUV; for (int i = 0; i < orderedVertices.Count; i++) { Vertex edgeStart = orderedVertices[i]; int edgeStartIndex = vertices.IndexOf(edgeStart); Vertex edgeEnd = orderedVertices[(i + 1) % orderedVertices.Count]; int edgeEndIndex = vertices.IndexOf(edgeEnd); if (edgeStartIndex == -1 || edgeEndIndex == -1) { continue; } if (lookup[edgeStartIndex] == lookup[localEdge.a] && lookup[edgeEndIndex] == lookup[localEdge.b]) { orderedVertices.InsertRange(i + 1, verticesToAppend); for (int j = 0; j < count; j++) { orderedSharedIndexes.Insert(i + j + 1, sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } else if (lookup[edgeStartIndex] == lookup[localEdge.b] && lookup[edgeEndIndex] == lookup[localEdge.a]) { verticesToAppend.Reverse(); orderedVertices.InsertRange(i + 1, verticesToAppend); for (int j = count - 1; j >= 0; j--) { orderedSharedIndexes.Insert(i + 1, sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } } data.vertices = orderedVertices; data.sharedIndexes = orderedSharedIndexes; data.sharedIndexesUV = orderedSharedUVIndexes; } } 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]; int vertexCount = vertices.Count; // triangulate and set new face indexes to end of current vertex list List <int> triangles; if (Triangulation.TriangulateVertices(data.vertices, out triangles, false)) { data.face = new Face(triangles); } 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> /// Apply a bevel to a set of edges. /// </summary> /// <param name="mesh">Target mesh.</param> /// <param name="edges">A set of edges to apply bevelling to.</param> /// <param name="amount">A value from 0 (bevel not at all) to 1 (bevel entire face).</param> /// <returns>The new faces created to form the bevel.</returns> public static List <Face> BevelEdges(ProBuilderMesh mesh, IList <Edge> edges, float amount) { if (mesh == null) { throw new ArgumentNullException("mesh"); } Dictionary <int, int> lookup = mesh.sharedVertexLookup; List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); List <EdgeLookup> m_edges = EdgeLookup.GetEdgeLookup(edges, lookup).Distinct().ToList(); List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh); List <FaceRebuildData> appendFaces = new List <FaceRebuildData>(); Dictionary <Face, List <int> > ignore = new Dictionary <Face, List <int> >(); HashSet <int> slide = new HashSet <int>(); int beveled = 0; Dictionary <int, List <SimpleTuple <FaceRebuildData, List <int> > > > holes = new Dictionary <int, List <SimpleTuple <FaceRebuildData, List <int> > > >(); // test every edge that will be moved along to make sure the bevel distance is appropriate. if it's not, adjust the max bevel amount // to suit. Dictionary <int, List <WingedEdge> > spokes = WingedEdge.GetSpokes(wings); HashSet <int> tested_common = new HashSet <int>(); foreach (EdgeLookup e in m_edges) { if (tested_common.Add(e.common.a)) { foreach (WingedEdge w in spokes[e.common.a]) { Edge le = w.edge.local; amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount); } } if (tested_common.Add(e.common.b)) { foreach (WingedEdge w in spokes[e.common.b]) { Edge le = w.edge.local; amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount); } } } if (amount < .001f) { Log.Info("Bevel Distance > Available Surface"); return(null); } // iterate selected edges and move each leading edge back along it's direction // storing information about adjacent faces in the process foreach (EdgeLookup lup in m_edges) { WingedEdge we = wings.FirstOrDefault(x => x.edge.Equals(lup)); if (we == null || we.opposite == null) { continue; } beveled++; ignore.AddOrAppend(we.face, we.edge.common.a); ignore.AddOrAppend(we.face, we.edge.common.b); ignore.AddOrAppend(we.opposite.face, we.edge.common.a); ignore.AddOrAppend(we.opposite.face, we.edge.common.b); // after initial slides go back and split indirect triangles at the intersecting index into two vertices slide.Add(we.edge.common.a); slide.Add(we.edge.common.b); SlideEdge(vertices, we, amount); SlideEdge(vertices, we.opposite, amount); appendFaces.AddRange(GetBridgeFaces(vertices, we, we.opposite, holes)); } if (beveled < 1) { Log.Info("Cannot Bevel Open Edges"); return(null); } // grab the "createdFaces" array now so that the selection returned is just the bridged faces // then add holes later var createdFaces = new List <Face>(appendFaces.Select(x => x.face)); Dictionary <Face, List <SimpleTuple <WingedEdge, int> > > sorted = new Dictionary <Face, List <SimpleTuple <WingedEdge, int> > >(); // sort the adjacent but affected faces into winged edge groups where each group contains a set of // unique winged edges pointing to the same face foreach (int c in slide) { IEnumerable <WingedEdge> matches = wings.Where(x => x.edge.common.Contains(c) && !(ignore.ContainsKey(x.face) && ignore[x.face].Contains(c))); HashSet <Face> used = new HashSet <Face>(); foreach (WingedEdge match in matches) { if (!used.Add(match.face)) { continue; } sorted.AddOrAppend(match.face, new SimpleTuple <WingedEdge, int>(match, c)); } } // now go through those sorted faces and apply the vertex exploding, keeping track of any holes created foreach (KeyValuePair <Face, List <SimpleTuple <WingedEdge, int> > > kvp in sorted) { // common index & list of vertices it was split into Dictionary <int, List <int> > appended; FaceRebuildData f = VertexEditing.ExplodeVertex(vertices, kvp.Value, amount, out appended); if (f == null) { continue; } appendFaces.Add(f); foreach (var apv in appended) { // organize holes by new face so that later we can compare the winding of the new face to the hole face // holes are sorted by key: common index value: face, vertex list holes.AddOrAppend(apv.Key, new SimpleTuple <FaceRebuildData, List <int> >(f, apv.Value)); } } FaceRebuildData.Apply(appendFaces, mesh, vertices); int removed = mesh.DeleteFaces(sorted.Keys).Length; mesh.sharedTextures = new SharedVertex[0]; mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal); // @todo don't rebuild indexes, keep 'em cached SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal; lookup = mesh.sharedVertexLookup; List <HashSet <int> > holesCommonIndexes = new List <HashSet <int> >(); // offset the indexes of holes and cull any potential holes that are less than 3 indexes (not a hole :) foreach (KeyValuePair <int, List <SimpleTuple <FaceRebuildData, List <int> > > > hole in holes) { // less than 3 indexes in hole path; ain't a hole if (hole.Value.Sum(x => x.item2.Count) < 3) { continue; } HashSet <int> holeCommon = new HashSet <int>(); foreach (SimpleTuple <FaceRebuildData, List <int> > path in hole.Value) { int offset = path.item1.Offset() - removed; for (int i = 0; i < path.item2.Count; i++) { holeCommon.Add(lookup[path.item2[i] + offset]); } } holesCommonIndexes.Add(holeCommon); } List <WingedEdge> modified = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face)); // now go through the holes and create faces for them vertices = new List <Vertex>(mesh.GetVertices()); List <FaceRebuildData> holeFaces = new List <FaceRebuildData>(); foreach (HashSet <int> h in holesCommonIndexes) { // even if a set of hole indexes made it past the initial culling, the distinct part // may have reduced the index count if (h.Count < 3) { continue; } // skip sorting the path if it's just a triangle if (h.Count < 4) { List <Vertex> v = new List <Vertex>(mesh.GetVertices(h.Select(x => sharedIndexes[x][0]).ToList())); holeFaces.Add(AppendElements.FaceWithVertices(v)); } // if this hole has > 3 indexes, it needs a tent pole triangulation, which requires sorting into the perimeter order else { List <int> holePath = WingedEdge.SortCommonIndexesByAdjacency(modified, h); List <Vertex> v = new List <Vertex>(mesh.GetVertices(holePath.Select(x => sharedIndexes[x][0]).ToList())); holeFaces.AddRange(AppendElements.TentCapWithVertices(v)); } } FaceRebuildData.Apply(holeFaces, mesh, vertices); mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal); // go through new faces and conform hole normals // get a hash of just the adjacent and bridge faces // HashSet<pb_Face> adjacent = new HashSet<pb_Face>(appendFaces.Select(x => x.face)); // and also just the filled holes HashSet <Face> newHoles = new HashSet <Face>(holeFaces.Select(x => x.face)); // now append filled holes to the full list of added faces appendFaces.AddRange(holeFaces); List <WingedEdge> allNewFaceEdges = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face)); for (int i = 0; i < allNewFaceEdges.Count && newHoles.Count > 0; i++) { WingedEdge wing = allNewFaceEdges[i]; if (newHoles.Contains(wing.face)) { newHoles.Remove(wing.face); // find first edge whose opposite face isn't a filled hole* then // conform normal by that. // *or is a filled hole but has already been conformed using (var it = new WingedEdgeEnumerator(wing)) { while (it.MoveNext()) { var w = it.Current; if (!newHoles.Contains(w.opposite.face)) { w.face.submeshIndex = w.opposite.face.submeshIndex; w.face.uv = new AutoUnwrapSettings(w.opposite.face.uv); SurfaceTopology.ConformOppositeNormal(w.opposite); break; } } } } } mesh.ToMesh(); return(createdFaces); }
public override bool RemoveActivity(T activityId) { // Retrieve the activity's node. if (!NodeLookup.TryGetValue(activityId, out Node <T, TActivity> node)) { return(false); } if (!node.Content.CanBeRemoved) { return(false); } RemoveUnsatisfiedSuccessorActivity(activityId); NodeLookup.Remove(node.Id); if (node.NodeType == NodeType.Isolated) { return(true); } if (node.NodeType == NodeType.End || node.NodeType == NodeType.Normal) { IList <T> incomingEdgeIds = node.IncomingEdges.ToList(); int length = incomingEdgeIds.Count; for (int i = 0; i < length; i++) { T edgeId = incomingEdgeIds[i]; Node <T, TActivity> tailNode = EdgeTailNodeLookup[edgeId]; // Remove the edge from the tail node. tailNode.OutgoingEdges.Remove(edgeId); EdgeTailNodeLookup.Remove(edgeId); if (!tailNode.OutgoingEdges.Any()) { if (tailNode.NodeType == NodeType.Normal) { tailNode.SetNodeType(NodeType.End); } else if (tailNode.NodeType == NodeType.Start) { tailNode.SetNodeType(NodeType.Isolated); } } // Remove the edge from the head node. node.IncomingEdges.Remove(edgeId); EdgeHeadNodeLookup.Remove(edgeId); if (!node.IncomingEdges.Any()) { if (node.NodeType == NodeType.Normal) { node.SetNodeType(NodeType.Start); } else if (node.NodeType == NodeType.End) { node.SetNodeType(NodeType.Isolated); } } // Remove the edge completely. EdgeLookup.Remove(edgeId); } } if (node.NodeType == NodeType.Start || node.NodeType == NodeType.Normal) { IList <T> outgoingEdgeIds = node.OutgoingEdges.ToList(); int length = outgoingEdgeIds.Count; for (int i = 0; i < length; i++) { T edgeId = outgoingEdgeIds[i]; Node <T, TActivity> headNode = EdgeHeadNodeLookup[edgeId]; // Remove the edge from the head node. headNode.IncomingEdges.Remove(edgeId); EdgeHeadNodeLookup.Remove(edgeId); if (!headNode.IncomingEdges.Any()) { if (headNode.NodeType == NodeType.Normal) { headNode.SetNodeType(NodeType.Start); } else if (headNode.NodeType == NodeType.End) { headNode.SetNodeType(NodeType.Isolated); } } // Remove the edge from the tail node. node.OutgoingEdges.Remove(edgeId); EdgeTailNodeLookup.Remove(edgeId); if (!node.OutgoingEdges.Any()) { if (node.NodeType == NodeType.Normal) { node.SetNodeType(NodeType.End); } else if (node.NodeType == NodeType.Start) { node.SetNodeType(NodeType.Isolated); } } // Remove the edge completely. EdgeLookup.Remove(edgeId); } } return(true); }