using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CaeGlobals; using CaeMesh.ContactSearchNamespace; namespace CaeMesh { public class ContactSearch { // Variables private FeMesh _mesh; private FeMesh _geometry; private double[][] _nodes; private BoundingBox[][] _cellBoundingBoxes; private BoundingBox[][] _cellEdgeBoundingBoxes; private int[][] _edgeCellBaseCellIds; private bool _includeSelfContact; private GroupContactPairsByEnum _groupContactPairsBy; // Properties public GroupContactPairsByEnum GroupContactPairsBy { get { return _groupContactPairsBy; } set { _groupContactPairsBy = value; } } // Constructors public ContactSearch(FeMesh mesh, FeMesh geometry) { _mesh = mesh; _geometry = geometry; // if (_mesh != null && _mesh.Nodes != null) { _nodes = new double[_mesh.MaxNodeId + 1][]; foreach (var nodeEntry in _mesh.Nodes) _nodes[nodeEntry.Key] = nodeEntry.Value.Coor; } // _includeSelfContact = false; _groupContactPairsBy = GroupContactPairsByEnum.None; // Get all edge cell base cell ids GetAllEdgeCellBaseCellIds(); } // Methods public List FindContactPairs(double distance, double angleDeg, GeometryFilterEnum filter, bool tryToResolve) { if (_mesh == null) return null; // Bounding boxes for each cell ComputeCellBoundingBoxes(distance, filter.HasFlag(GeometryFilterEnum.IgnoreHidden)); // Find all surfaces of the assembly ContactSurface[] contactSurfaces = GetAllContactSurfaces(distance, filter); // Find all surface pairs in contact double angleRad = angleDeg * Math.PI / 180; List contactSurfacePairs = new List(); // for (int i = 0; i < contactSurfaces.Length; i++) { for (int j = i + 1; j < contactSurfaces.Length; j++) { if (CheckSurfaceToSurfaceDistance(contactSurfaces[i], contactSurfaces[j], distance, angleRad)) { contactSurfacePairs.Add(new ContactSurface[] { contactSurfaces[i], contactSurfaces[j] }); } } } // Group surface pairs if (_groupContactPairsBy == GroupContactPairsByEnum.None) return GroupContactPairsByNone(contactSurfacePairs, tryToResolve); else if (_groupContactPairsBy == GroupContactPairsByEnum.BySurfaceAngle) throw new NotSupportedException(); else if (_groupContactPairsBy == GroupContactPairsByEnum.ByParts) return GroupContactPairsByParts(contactSurfacePairs, tryToResolve); else throw new NotSupportedException(); } private void ComputeCellBoundingBoxes(double distance, bool ignoreHidden) { // Bounding boxes for each cell int[] cell; BoundingBox bb; _cellBoundingBoxes = new BoundingBox[_mesh.GetMaxPartId() + 1][]; _cellEdgeBoundingBoxes = new BoundingBox[_mesh.GetMaxPartId() + 1][]; // foreach (var partEntry in _mesh.Parts) { if (ignoreHidden && !partEntry.Value.Visible) continue; // _cellBoundingBoxes[partEntry.Value.PartId] = new BoundingBox[partEntry.Value.Visualization.Cells.Length]; for (int i = 0; i < partEntry.Value.Visualization.Cells.Length; i++) { cell = partEntry.Value.Visualization.Cells[i]; bb = new BoundingBox(); bb.IncludeFirstCoor(_nodes[cell[0]]); bb.IncludeCoorFast(_nodes[cell[1]]); bb.IncludeCoorFast(_nodes[cell[2]]); if (cell.Length == 4 || cell.Length == 8) bb.IncludeCoorFast(_nodes[cell[3]]); bb.Inflate(distance * 0.5); // _cellBoundingBoxes[partEntry.Value.PartId][i] = bb; } // Shell edge faces if (partEntry.Value.PartType == PartType.Shell) { _cellEdgeBoundingBoxes[partEntry.Value.PartId] = new BoundingBox[partEntry.Value.Visualization.EdgeCells.Length]; for (int i = 0; i < partEntry.Value.Visualization.EdgeCells.Length; i++) { cell = partEntry.Value.Visualization.EdgeCells[i]; bb = new BoundingBox(); bb.IncludeFirstCoor(_nodes[cell[0]]); bb.IncludeCoorFast(_nodes[cell[1]]); if (cell.Length == 3) bb.IncludeCoorFast(_nodes[cell[2]]); bb.Inflate(distance * 0.5); // _cellEdgeBoundingBoxes[partEntry.Value.PartId][i] = bb; } } } } private void GetAllEdgeCellBaseCellIds() { _edgeCellBaseCellIds = new int[_mesh.GetMaxPartId() + 1][]; foreach (var partEntry in _mesh.Parts) { _edgeCellBaseCellIds[partEntry.Value.PartId] = partEntry.Value.Visualization.GetAllEdgeCellBaseCellIds(); } } private ContactSurface[] GetAllContactSurfaces(double distance, GeometryFilterEnum filter) { HashSet freeEdgeIds; ContactSurface contactSurface; List contactSurfacesList = new List(); // foreach (var partEntry in _mesh.Parts) { if (filter.HasFlag(GeometryFilterEnum.IgnoreHidden) && !partEntry.Value.Visible) continue; // if (filter.HasFlag(GeometryFilterEnum.Solid) && partEntry.Value.PartType == PartType.Solid) { // Solid faces for (int i = 0; i < partEntry.Value.Visualization.FaceCount; i++) { contactSurfacesList.Add(new ContactSurface(_mesh, _nodes, partEntry.Value, i, GeometryType.SolidSurface, distance * 0.5)); } } else if (partEntry.Value.PartType == PartType.Shell) { // Shell faces if (filter.HasFlag(GeometryFilterEnum.Shell)) { for (int i = 0; i < partEntry.Value.Visualization.FaceCount; i++) { // Front face contactSurface = new ContactSurface(_mesh, _nodes, partEntry.Value, i, GeometryType.ShellFrontSurface, distance * 0.5); contactSurfacesList.Add(contactSurface); // Back face contactSurface = new ContactSurface(contactSurface); // copy contactSurface.ConvertToShellBackSurface(); contactSurfacesList.Add(contactSurface); } } // Shell edge faces if (filter.HasFlag(GeometryFilterEnum.ShellEdge)) { freeEdgeIds = partEntry.Value.Visualization.GetFreeEdgeIds(); for (int i = 0; i < partEntry.Value.Visualization.EdgeCount; i++) { contactSurface = new ContactSurface(_mesh, _nodes, partEntry.Value, i, GeometryType.ShellEdgeSurface, distance * 0.5); // Fnd internal edges on shells if (!freeEdgeIds.Contains(i)) contactSurface.Internal = true; contactSurfacesList.Add(contactSurface); } } } } // ContactSurface[] contactSurfaces = contactSurfacesList.ToArray(); // Find internal surfaces on compound parts for (int i = 0; i < contactSurfaces.Length; i++) { for (int j = i + 1; j < contactSurfaces.Length; j++) { if (contactSurfaces[i].GeometryType == contactSurfaces[j].GeometryType && // skip front and back shell contactSurfaces[i].NodeIds.Count() == contactSurfaces[j].NodeIds.Count() && contactSurfaces[i].BoundingBox.Intersects(contactSurfaces[j].BoundingBox) && contactSurfaces[i].NodeIds.Union(contactSurfaces[j].NodeIds).Count() == contactSurfaces[i].NodeIds.Count) { contactSurfaces[i].Internal = true; contactSurfaces[j].Internal = true; } } } return contactSurfaces; } private bool CheckSurfaceToSurfaceDistance(ContactSurface cs1, ContactSurface cs2, double distance, double angleRad) { if (cs1.Internal || cs2.Internal) return false; if (!_includeSelfContact && cs1.Part == cs2.Part) return false; if (!cs1.BoundingBox.Intersects(cs2.BoundingBox)) return false; // BoundingBox bb1; BoundingBox bb2; BoundingBox[] bbs1; BoundingBox[] bbs2; BoundingBox intersection = cs1.BoundingBox.GetIntersection(cs2.BoundingBox); // int[] cell1; int[] cell2; int[] cell1Ids; int[] cell2Ids; double[] cellNorm = new double[3]; double[] edgeCellNorm = new double[3]; double[][] t1 = new double[3][]; double[][] t2 = new double[3][]; t1[2] = new double[3]; t2[2] = new double[3]; // bool edgeSurface1 = cs1.GeometryType == GeometryType.ShellEdgeSurface; bool edgeSurface2 = cs2.GeometryType == GeometryType.ShellEdgeSurface; bool allowPenetration = (edgeSurface1 && edgeSurface2) || (cs1.GeometryType == GeometryType.SolidSurface && cs1.GeometryType == GeometryType.SolidSurface); bool onlyInternal = (cs1.GeometryType == GeometryType.SolidSurface || cs1.GeometryType == GeometryType.ShellFrontSurface || cs1.GeometryType == GeometryType.ShellBackSurface) && (cs2.GeometryType == GeometryType.SolidSurface || cs2.GeometryType == GeometryType.ShellFrontSurface || cs2.GeometryType == GeometryType.ShellBackSurface); onlyInternal = false; // Use face cell ids or edge cell ids if (edgeSurface1) { bbs1 = _cellEdgeBoundingBoxes[cs1.Part.PartId]; cell1Ids = cs1.Part.Visualization.EdgeCellIdsByEdge[cs1.Id]; } else { bbs1 = _cellBoundingBoxes[cs1.Part.PartId]; cell1Ids = cs1.Part.Visualization.CellIdsByFace[cs1.Id]; } // foreach (int cell1Id in cell1Ids) { bb1 = bbs1[cell1Id]; // if (bb1.Intersects(intersection)) { // Use face cell ids or edge cell ids if (edgeSurface2) { bbs2 = _cellEdgeBoundingBoxes[cs2.Part.PartId]; cell2Ids = cs2.Part.Visualization.EdgeCellIdsByEdge[cs2.Id]; } else { bbs2 = _cellBoundingBoxes[cs2.Part.PartId]; cell2Ids = cs2.Part.Visualization.CellIdsByFace[cs2.Id]; } // foreach (int cell2Id in cell2Ids) { bb2 = bbs2[cell2Id]; // if (bb1.Intersects(bb2)) { cell1 = cs1.GetCell(cell1Id); // reverse cell ids for shell back faces or get shell edge cell cell2 = cs2.GetCell(cell2Id); // reverse cell ids for shell back faces or get shell edge cell // Cell 1 triangle 1 t1[0] = _nodes[cell1[0]]; t1[1] = _nodes[cell1[1]]; if (!edgeSurface1) t1[2] = _nodes[cell1[2]]; // Cell 2 triangle 1 t2[0] = _nodes[cell2[0]]; t2[1] = _nodes[cell2[1]]; if (!edgeSurface2) t2[2] = _nodes[cell2[2]]; // 1. triangle is edge segment - create a triangle out of it if (edgeSurface1) { cs1.GetEdgeCellNormal(cell1Id, _edgeCellBaseCellIds[cs1.Part.PartId][cell1Id], ref cellNorm, ref edgeCellNorm); Geometry.VpVxS(ref t1[2], t1[1], cellNorm, 0.001 * distance); } // 2. triangle is edge segment - create a triangle out of it if (edgeSurface2) { cs2.GetEdgeCellNormal(cell2Id, _edgeCellBaseCellIds[cs2.Part.PartId][cell2Id], ref cellNorm, ref edgeCellNorm); Geometry.VpVxS(ref t2[2], t2[1], cellNorm, 0.001 * distance); } if (CheckTriangleToTriangleDistance(t1, t2, distance, angleRad, allowPenetration, onlyInternal)) return true; // Cell 2 is a rectangle if (cell2.Length == 4 || cell2.Length == 8) { // Cell 2 triangle 2 t2[0] = _nodes[cell2[0]]; t2[1] = _nodes[cell2[2]]; t2[2] = _nodes[cell2[3]]; // if (CheckTriangleToTriangleDistance(t1, t2, distance, angleRad, allowPenetration, onlyInternal)) return true; } // Cell 1 is a rectangle if (cell1.Length == 4 || cell1.Length == 8) { // Cell 1 triangle 2 t1[0] = _nodes[cell1[0]]; t1[1] = _nodes[cell1[2]]; t1[2] = _nodes[cell1[3]]; // if (CheckTriangleToTriangleDistance(t1, t2, distance, angleRad, allowPenetration, onlyInternal)) return true; // Cell 2 triangle 1 again if (cell2.Length == 4 || cell2.Length == 8) { t2[0] = _nodes[cell2[0]]; t2[1] = _nodes[cell2[1]]; t2[2] = _nodes[cell2[2]]; // if (CheckTriangleToTriangleDistance(t1, t2, distance, angleRad, allowPenetration, onlyInternal)) return true; } } } } } } return false; } private bool CheckTriangleToTriangleDistance(double[][] t1, double[][] t2, double distance, double angleRad, bool penetrationPossible, bool onlyInternal) { double dist; double ang; // double[] a = new double[3]; double[] b = new double[3]; double[] c = new double[3]; double[] d = new double[3]; double[] n1 = new double[3]; double[] n2 = new double[3]; // Triangle 1 Geometry.VmV(ref a, t1[1], t1[0]); Geometry.VmV(ref b, t1[2], t1[0]); Geometry.VcrossV(ref n1, a, b); Geometry.VxS(ref n1, n1, 1 / Math.Sqrt(Geometry.VdotV(n1, n1))); // Triangle 2 Geometry.VmV(ref c, t2[1], t2[0]); Geometry.VmV(ref d, t2[2], t2[0]); Geometry.VcrossV(ref n2, c, d); Geometry.VxS(ref n2, n2, 1 / Math.Sqrt(Geometry.VdotV(n2, n2))); // double vDotV = Geometry.Clamp(Geometry.VdotV(n1, n2), -1, 1); // 180° - the normal point in the opposite directions ang = Math.PI - Math.Acos(vDotV); if (ang < angleRad) { // Closest points on triangles t1 and t2 are p adn q, respectively double[] p = new double[3]; double[] q = new double[3]; double[] pq = new double[3]; // dist = Geometry.TriDist(ref p, ref q, t1, t2, onlyInternal); // if (dist < distance) { // Check the orientation of the triangle normal if (dist > 0) { double ang1; double ang2; // pq is a vector from p to q Geometry.VmV(ref pq, q, p); Geometry.Vnorm(ref pq, pq); ang1 = Geometry.VdotV(pq, n1); if (!penetrationPossible && ang1 < 0) return false; // negative value means n1 points away from q ang2 = Geometry.VdotV(pq, n2); if (!penetrationPossible && ang2 > 0) return false; // positive value means n2 points away from p // Check that the closest triangle points are one above the other // The angle between the closest direction vector and normal must be small < 5° = 0.995 if (Math.Abs(ang1) < 0.995 && Math.Abs(ang2) < 0.995) return false; } // Shrink the triangles and try again - the triangles should not overlap only over an vertex or an edge double[][] t1s = Geometry.ShrinkTriangle(t1, 3 * distance); double[][] t2s = Geometry.ShrinkTriangle(t2, 3 * distance); dist = Geometry.TriDist(ref p, ref q, t1s, t2s, false); if (dist > distance) return false; // return true; } } return false; } // private List GroupContactPairsByNone(List contactSurfacePairs, bool tryToResolve) { List masterSlaveItems = new List(); // foreach (var csp in contactSurfacePairs) { masterSlaveItems.Add(new MasterSlaveItem(csp[0].Part.Name, csp[1].Part.Name, new HashSet() { csp[0].GeometryId }, new HashSet() { csp[1].GeometryId })); } // ContactGraph contactGraph = new ContactGraph(); contactGraph.AddMasterSlaveItems(masterSlaveItems, _mesh); masterSlaveItems = contactGraph.GetMasterSlaveItems(tryToResolve); // return masterSlaveItems; } private List GroupContactPairsByParts(List contactSurfacePairs, bool tryToResolve) { int i; int j; int iType; int jType; int[] key; MasterSlaveItem masterSlaveItem; CompareIntArray comparer = new CompareIntArray(); Dictionary partKeyMasterSlaveItems = new Dictionary(comparer); // Dictionary partIds = GetPartIdsMergedByCompounds(); // Merge by part Id foreach (var csp in contactSurfacePairs) { if (partIds[csp[0].Part.PartId] < partIds[csp[1].Part.PartId]) { i = 0; j = 1; } else { i = 1; j = 0; } // if (csp[i].GeometryType == GeometryType.ShellEdgeSurface) iType = 1; else iType = 0; if (csp[j].GeometryType == GeometryType.ShellEdgeSurface) jType = 1; else jType = 0; // key = new int[] { partIds[csp[i].Part.PartId], iType, partIds[csp[j].Part.PartId], jType }; if (partKeyMasterSlaveItems.TryGetValue(key, out masterSlaveItem)) { masterSlaveItem.MasterGeometryIds.Add(csp[i].GeometryId); masterSlaveItem.SlaveGeometryIds.Add(csp[j].GeometryId); } else { masterSlaveItem = new MasterSlaveItem(csp[i].Part.Name, csp[j].Part.Name, new HashSet(), new HashSet()); masterSlaveItem.MasterGeometryIds.Add(csp[i].GeometryId); masterSlaveItem.SlaveGeometryIds.Add(csp[j].GeometryId); partKeyMasterSlaveItems.Add(key, masterSlaveItem); } } // ContactGraph contactGraph = new ContactGraph(); contactGraph.AddMasterSlaveItems(partKeyMasterSlaveItems.Values, _mesh); List masterSlaveItems = contactGraph.GetMasterSlaveItems(tryToResolve); // return masterSlaveItems; } private Dictionary GetPartIdsMergedByCompounds() { int maxPartId = 0; Dictionary partIds = new Dictionary(); foreach (var partEntry in _mesh.Parts) { partIds.Add(partEntry.Value.PartId, partEntry.Value.PartId); if (partEntry.Value.PartId > maxPartId) maxPartId = partEntry.Value.PartId; } // if (_geometry != null && _geometry.Parts != null) { BasePart part; foreach (var geometryPartEntry in _geometry.Parts) { if (geometryPartEntry.Value is CompoundGeometryPart cgp) { foreach (string subPartName in cgp.SubPartNames) { if (_mesh.Parts.TryGetValue(subPartName, out part)) partIds[part.PartId] = maxPartId; } maxPartId++; } } } // return partIds; } } }