/// <summary> /// Create and add a face - special version for <see cref="SMeshBoundaryExtensions.GetBoundaryFaces(SMeshData, bool)"/> /// <para> /// A face is only "added once", i.e. when two elements share the face, it is found twice, /// once defined as "toNode"-"fromNode" and once as "fromNode"-"toNode". The second time, /// the existing face is being reused, and the element is added as the <see cref="CMeshFace.RightElement"/> /// </para> /// <para> /// The <see cref="CMeshFace"/> is added to the global list of faces, and also to tne nodes list of faces. /// </para> /// </summary> internal static void AddFace(int element, int fromNode, int toNode, List <CMeshFace>[] facesFromNode) { List <CMeshFace> facesFromToNode = facesFromNode[toNode]; // Try find "reverse face" going from from-node to to-node. // The FindIndex with delegate is 10+ times slower than the tight loop below. //int reverseFaceIndex = toNodeFaces.FindIndex(mf => mf.ToNode == fromNode); int reverseFaceIndex = -1; for (int i = 0; i < facesFromToNode.Count; i++) { if (facesFromToNode[i].ToNode == fromNode) { reverseFaceIndex = i; break; } } if (reverseFaceIndex >= 0) { // Found reverse face, reuse it and add the element as the RightElement CMeshFace reverseFace = facesFromToNode[reverseFaceIndex]; reverseFace.RightElement = element; } else { // Found new face, set element as LeftElement and add it to both from-node and to-node CMeshFace meshFace = new CMeshFace(fromNode, toNode) { LeftElement = element, }; facesFromNode[fromNode].Add(meshFace); } }
/// <summary> /// Build boundary polygon. Returns either a <see cref="Polygon"/> or <see cref="MultiPolygon"/> /// depending on whether the mesh is fully connected or consist of independent parts. /// <para> /// To always return a <see cref="MultiPolygon"/>, set the <paramref name="alwaysMultiPolygon"/> to true. /// </para> /// </summary> private static Geometry BuildBoundaryGeometry(CMeshData mesh, List <CMeshFace> boundaryFaces, bool alwaysMultiPolygon) { // There will be one polygon for each connected sub mesh, in case there is more than one. //System.Diagnostics.Stopwatch timer = MeshExtensions.StartTimer(); // Find all connected sub meshes. SubMeshes subMeshes = mesh.FindConnectedSubMeshes(); //timer.ReportAndRestart("FindConnectedSubMeshes " + subMeshes.NumberOfSubMeshes); BoundarySegmentsBuilder bsb = new BoundarySegmentsBuilder(mesh); if (subMeshes.NumberOfSubMeshes == 1) { Polygon boundaryPoly = BuildSubMeshBoundaryGeometry(mesh, bsb, boundaryFaces); if (!alwaysMultiPolygon) { return(boundaryPoly); } MultiPolygon multiPolygon = new MultiPolygon(new Polygon[] { boundaryPoly }); return(multiPolygon); } else // More than one sub-mesh - make a Polygon for each sub mesh { // Find boundary faces for each sub mesh List <List <CMeshFace> > subMeshesBoundaryFaces = new List <List <CMeshFace> >(subMeshes.NumberOfSubMeshes); for (int i = 0; i < subMeshes.NumberOfSubMeshes; i++) { subMeshesBoundaryFaces.Add(new List <CMeshFace>()); } for (int i = 0; i < boundaryFaces.Count; i++) { CMeshFace bcFace = boundaryFaces[i]; int subMeshId = subMeshes.ElmtSubMesh[bcFace.LeftElement]; // subMeshId's starts from 1 subMeshesBoundaryFaces[subMeshId - 1].Add(bcFace); } // Make boundary polygon for each sub mesh - ordered as in SubMeshInfos to get largest parts first List <Polygon> polygons = new List <Polygon>(subMeshes.NumberOfSubMeshes); foreach (SubMeshInfo subMeshInfo in subMeshes.SubMeshInfos) { List <CMeshFace> subMeshBoundaryFaces = subMeshesBoundaryFaces[subMeshInfo.SubMeshId - 1]; Polygon subMeshBoundaryPoly = BuildSubMeshBoundaryGeometry(mesh, bsb, subMeshBoundaryFaces); polygons.Add(subMeshBoundaryPoly); } MultiPolygon multiPoly = new MultiPolygon(polygons.ToArray()); return(multiPoly); } }
/// <summary> /// Provided a list of all faces in the mesh, extract faces on the boundary - unsorted /// </summary> public static List <CMeshFace> ExtractBoundaryFaces(List <CMeshFace> meshFaces) { List <CMeshFace> bcs = new List <CMeshFace>(); for (int i = 0; i < meshFaces.Count; i++) { CMeshFace meshFace = meshFaces[i]; if (meshFace.IsBoundaryFace()) { bcs.Add(meshFace); } } return(bcs); }
/// <summary> /// Depth First Search visit method /// </summary> /// <param name="elementIndex">Start element</param> /// <param name="subMeshId">Sub mesh ID</param> /// <returns>Number of elements in sub mesh</returns> private int BFSVisit(int elementIndex, int subMeshId) { int numElmtsInSubMesh = 0; // Element has been discovered _stack.Push(elementIndex); ElmtSubMesh[elementIndex] = subMeshId; numElmtsInSubMesh++; while (_stack.Count > 0) { int elmt = _stack.Pop(); List <int> elmtFaces = _meshData.ElementsFaces[elmt]; for (int i = 0; i < elmtFaces.Count; i++) { // Find element on the other side of the face CMeshFace elmtFace = _meshData.Faces[elmtFaces[i]]; // Boundary faces never has an element on the other side if (elmtFace.IsBoundaryFace()) { continue; } int otherElmt; if (elmtFace.LeftElement == elmt) { otherElmt = elmtFace.RightElement; } else { otherElmt = elmtFace.LeftElement; } // Check if we have already visited otherElmt if (ElmtSubMesh[otherElmt] == 0) { // Element has been discovered _stack.Push(otherElmt); ElmtSubMesh[otherElmt] = subMeshId; numElmtsInSubMesh++; } } } return(numElmtsInSubMesh); }
/// <summary> /// Return all boundary faces. Unsorted /// </summary> /// <param name="meshData">MeshData object to get boundary faces for</param> /// <param name="checkAllFaces">In case boundary codes are set incorrectly, this will check all faces</param> public static List <CMeshFace> GetBoundaryFaces(this CMeshData meshData, bool checkAllFaces) { //System.Diagnostics.Stopwatch timer = MeshExtensions.StartTimer(); List <CMeshFace>[] facesFromNode = new List <CMeshFace> [meshData.NumberOfNodes]; // Preallocate list of face on all nodes - used in next loop for (int i = 0; i < meshData.NumberOfNodes; i++) { facesFromNode[i] = new List <CMeshFace>(); } // Create all potential boundary faces - those having // boundary code on both to-node and from-node // (not all those need to be boundary faces). for (int ielmt = 0; ielmt < meshData.NumberOfElements; ielmt++) { CreateAddElementFaces(meshData, facesFromNode, ielmt, checkAllFaces); } // Figure out boundary code and store all boundary faces List <CMeshFace> boundaryFaces = new List <CMeshFace>(); for (int i = 0; i < facesFromNode.Length; i++) { List <CMeshFace> facesFromThisNode = facesFromNode[i]; for (int j = 0; j < facesFromThisNode.Count; j++) { CMeshFace face = facesFromThisNode[j]; // Only take those with fromNode matching this node - // otherwise they are taken twice. //if (face.FromNode.Index == i) { face.SetBoundaryCode(meshData); if (face.IsBoundaryFace()) { boundaryFaces.Add(face); } } } } //timer.Report("GetBoundaryFaces"); return(boundaryFaces); }
/// <summary> /// Create and add a face. /// <para> /// A face is only "added once", i.e. when two elements share the face, it is found twice, /// once defined as "toNode"-"fromNode" and once as "fromNode"-"toNode". The second time, /// the existing face is being reused, and the element is added as the <see cref="CMeshFace.RightElement"/> /// </para> /// <para> /// The <see cref="CMeshFace"/> is added to the global list of faces, and also to tne nodes list of faces. /// </para> /// </summary> private void AddFace(int element, int fromNode, int toNode) { List <int> fromNodeFaces = NodesFaces[fromNode]; List <int> toNodeFaces = NodesFaces[toNode]; // Try find "reverse face" going from to-node to from-node. // The FindIndex with delegate is 10+ times slower than the tight loop below. //int reverseFaceIndex = toNodeFaces.FindIndex(mf => mf.ToNode == fromNode); int reverseToNodeFaceIndex = -1; // Look in all faces starting from toNode for (int i = 0; i < toNodeFaces.Count; i++) { // Check if the face goes to fromNode if (Faces[toNodeFaces[i]].ToNode == fromNode) { reverseToNodeFaceIndex = i; break; } } if (reverseToNodeFaceIndex >= 0) { // Found reverse face, reuse it and add the element as the RightElement CMeshFace reverseFace = Faces[toNodeFaces[reverseToNodeFaceIndex]]; reverseFace.RightElement = element; } else { // Found new face, set element as LeftElement and add it to both from-node and to-node CMeshFace meshFace = new CMeshFace(fromNode, toNode) { LeftElement = element, }; Faces.Add(meshFace); fromNodeFaces.Add(Faces.Count - 1); // Adding to toNodeFaces is not required for the algorithm to work, // however, it is required in order to get NodesFaces lists right toNodeFaces.Add(Faces.Count - 1); } }
/// <summary> /// Build faces of mesh. It will by default build <see cref="Faces"/> and the /// <see cref="NodesFaces"/>. It will only build the <see cref="ElementsFaces"/> /// if the <paramref name="elmtFaces"/> flag is set. /// </summary> /// <returns>String of errors. Null if no errors was found</returns> public List <string> BuildFaces(bool elmtFaces = false) { List <string> errors = new List <string>(); int numberOfNodes = NumberOfNodes; int numberOfElements = NumberOfElements; bool hasElementFaces = ElementsFaces != null; //System.Diagnostics.Stopwatch timer = MeshExtensions.StartTimer(); if (Faces == null) { // Build up face lists // The exact number of faces is: NumberOfElements+NumberOfNodes + numberOfSubMeshes - numberOfHoles Faces = new List <CMeshFace>((int)((numberOfElements + numberOfNodes) * 1.01)); if (elmtFaces) { ElementsFaces = new List <int> [numberOfElements]; } // Preallocate list of face on all nodes - used in next loop NodesFaces = new List <int> [numberOfNodes]; for (int i = 0; i < numberOfNodes; i++) { NodesFaces[i] = new List <int>(); } //timer.ReportAndRestart("Prealloc nodeface"); //watch.Start(); // Create all faces. for (int ielmt = 0; ielmt < numberOfElements; ielmt++) { int element = ielmt; int[] elmtNodes = _connectivity[element]; if (elmtFaces) { ElementsFaces[ielmt] = new List <int>(elmtNodes.Length); } for (int j = 0; j < elmtNodes.Length; j++) { int fromNode = elmtNodes[j]; int toNode = elmtNodes[(j + 1) % elmtNodes.Length]; AddFace(element, fromNode, toNode); } } //timer.ReportAndRestart("Create faces "+Faces.Count); // Figure out boundary code for (int i = 0; i < Faces.Count; i++) { CMeshFace face = Faces[i]; face.SetBoundaryCode(this, errors); } //timer.ReportAndRestart("Set Boundary Code"); } if (elmtFaces && !hasElementFaces) { // If not already created, create the lists if (ElementsFaces == null) { ElementsFaces = new List <int> [numberOfElements]; for (int ielmt = 0; ielmt < numberOfElements; ielmt++) { ElementsFaces[ielmt] = new List <int>(); } } // Add face to the elements list of faces for (int i = 0; i < Faces.Count; i++) { CMeshFace face = Faces[i]; ElementsFaces[face.LeftElement].Add(i); if (face.RightElement >= 0) { ElementsFaces[face.RightElement].Add(i); } } } //timer.ReportAndRestart("Create element faces"); return(errors); }
/// <summary> /// Build boundary segments, i.e. connect the faces to lines when possible. /// If provided all mesh faces, this will provide the outer boundary as /// one segment and all holes in the mesh etc. as individual section. /// There is no guarantee that the first one is the outer boundary. /// </summary> public List <LinkedList <int> > BuildBoundarySegments(List <CMeshFace> faces) { if (faces.Count == 0) { return(null); } for (int iface = 0; iface < faces.Count; iface++) { int fromNode = faces[iface].FromNode; if (_fromNodeFace[fromNode] == -1) { // The first time the fromNode is a from-node _fromNodeFace[fromNode] = iface; } else if (_fromNodeFace[fromNode] == -2) { // The third or even more time the fromNode is a from-node _fromNodeFaceDict[fromNode].Add(iface); } else { // The second time the fromNode is a from-node List <int> list = new List <int>(); list.Add(_fromNodeFace[fromNode]); list.Add(iface); _fromNodeFaceDict.Add(fromNode, list); _fromNodeFace[fromNode] = -2; } } // Array telling which boundary segment a given face belongs to int[] faceSegmentIndex = new int[faces.Count]; for (int ii = 0; ii < faceSegmentIndex.Length; ii++) { faceSegmentIndex[ii] = -1; } // All segments build by the list of faces List <LinkedList <int> > segments = new List <LinkedList <int> >(); // Make sure to visit all faces for (int i = 0; i < faces.Count; i++) { // Check if this face has already been visited. if (faceSegmentIndex[i] >= 0) { continue; } // Start new boundary segment with face i int currentSegmentIndex = segments.Count; int currentFaceIndex = i; LinkedList <int> currentSegment = new LinkedList <int>(); // Add current face to segment currentSegment.AddLast(currentFaceIndex); faceSegmentIndex[currentFaceIndex] = currentSegmentIndex; while (true) { // Try find next face, which is the face with fromNode matching currentFace.ToNode CMeshFace currentFace = faces[currentFaceIndex]; int nextFaceIndex = NextFaceFromNode(currentFace.ToNode, _fromNodeFace, _fromNodeFaceDict); if (nextFaceIndex < 0) { // No to-node, we are done with this segment segments.Add(currentSegment); break; } // Check if the next face is already part of a segment if (faceSegmentIndex[nextFaceIndex] >= 0) { if (faceSegmentIndex[nextFaceIndex] == currentSegmentIndex) { // Circular boundary - we are done with this segment segments.Add(currentSegment); break; } // Now: nextSegment is not the same as the currentSection, // but they should be - move entire current segment to the // start of the nextFace segment int nextFaceSegmentIndex = faceSegmentIndex[nextFaceIndex]; LinkedList <int> nextSegment = segments[nextFaceSegmentIndex]; // Move all faces from currentSegment to nextFaceSegment LinkedListNode <int> thisSegmentListNode = currentSegment.Last; while (thisSegmentListNode != null) { int faceToMoveIndex = thisSegmentListNode.Value; nextSegment.AddFirst(faceToMoveIndex); faceSegmentIndex[faceToMoveIndex] = nextFaceSegmentIndex; thisSegmentListNode = thisSegmentListNode.Previous; } break; // Break out of while (true) loop } // Next face is not already part of a segment, add it to the end of this segment // Make nextFace to currentFace - add it to the list of current segments. currentFaceIndex = nextFaceIndex; currentSegment.AddLast(currentFaceIndex); faceSegmentIndex[currentFaceIndex] = currentSegmentIndex; } } // Reset fromNodeFace array to initial value - in case of more than one sub mesh for (int iface = 0; iface < faces.Count; iface++) { int fromNode = faces[iface].FromNode; _fromNodeFace[fromNode] = -1; } _fromNodeFaceDict.Clear(); return(segments); }
private static Polygon BuildSubMeshBoundaryGeometry(CMeshData mesh, BoundarySegmentsBuilder bsb, List <CMeshFace> boundaryFaces) { // Find connected segments List <LinkedList <int> > segments = bsb.BuildBoundarySegments(boundaryFaces); if (segments == null) { return(null); } LinearRing shell = null; List <LinearRing> holes = new List <LinearRing>(); // Create mesh boundary with segments for (int isegment = 0; isegment < segments.Count; isegment++) { LinkedList <int> segment = segments[isegment]; CMeshFace first = boundaryFaces[segment.First.Value]; CMeshFace last = boundaryFaces[segment.Last.Value]; if (!NodeEquals(first.FromNode, last.ToNode)) { Console.Out.WriteLine("Skipping: {0,4} {1,8} {2,8} {3,8}", isegment, segment.Count, first.FromNode, last.ToNode); continue; } Coordinate[] coords = new Coordinate[segment.Count + 1]; int i = 0; foreach (int iFace in segment) { CMeshFace face = boundaryFaces[iFace]; coords[i++] = new Coordinate(mesh.X[face.FromNode], mesh.Y[face.FromNode]); } coords[segment.Count] = new Coordinate(mesh.X[last.ToNode], mesh.Y[last.ToNode]); LinearRing ring = new LinearRing(coords); if (ring.IsCCW) { if (shell != null) { throw new Exception("Finding two shells of a connected sub-mesh"); } shell = ring; } else { holes.Add(ring); } } Polygon p; if (holes.Count > 0) { p = new Polygon(shell, holes.ToArray()); } else { p = new Polygon(shell); } return(p); }
/// <summary> /// Build list of <see cref="CMeshBoundary"/>, one for each boundary code, based on the <paramref name="meshFaces"/> /// <para> /// The <paramref name="meshFaces"/> need only contain boundary faces. Internal faces are ignored. /// </para> /// </summary> private static List <CMeshBoundary> BuildBoundaryList(CMeshData mesh, List <CMeshFace> meshFaces) { // Sort all faces on boundary code, assuming code numbers does not grow very big. List <List <CMeshFace> > bcs = new List <List <CMeshFace> >(); for (int i = 0; i < meshFaces.Count; i++) { CMeshFace meshFace = meshFaces[i]; if (meshFace.IsBoundaryFace()) { while (meshFace.Code + 1 > bcs.Count) { List <CMeshFace> boundaryFaces = new List <CMeshFace>(); bcs.Add(boundaryFaces); } bcs[meshFace.Code].Add(meshFace); } } List <CMeshBoundary> boundaries = new List <CMeshBoundary>(); BoundarySegmentsBuilder bsb = new BoundarySegmentsBuilder(mesh); // For each boundary code, find segments for (int ic = 0; ic < bcs.Count; ic++) { int code = ic; List <CMeshFace> faces = bcs[ic]; List <LinkedList <int> > segments = bsb.BuildBoundarySegments(faces); if (segments == null) { continue; } // Create mesh boundary with segments CMeshBoundary meshBoundary = new CMeshBoundary() { Code = code }; foreach (LinkedList <int> segment in segments) { if (segment == null) { continue; } List <CMeshFace> segmentFaces = new List <CMeshFace>(segment.Count); foreach (int currentFace in segment) { segmentFaces.Add(faces[currentFace]); } meshBoundary.Segments.Add(segmentFaces); } boundaries.Add(meshBoundary); } //// Sort on boundary codes - well, they are created in code-order //boundaries.Sort((mb1, mb2) => mb1.Code.CompareTo(mb2.Code)); return(boundaries); }