/// <summary> /// get interatomic contacts /// </summary> /// <param name="treeA"></param> /// <param name="treeB"></param> /// <returns></returns> public ChainContactInfo GetAnyChainContactInfo(BVTree treeA, BVTree treeB) { chainContactInfo = new ChainContactInfo(); if (BVTree.treeType == '1') { IntersectResidues(treeA, treeB); /* if (chainContactInfo.atomContactHash.Count == 0 || chainContactInfo.cbetaContactHash.Count == 0) * { * chainContactInfo = null; * return null; * }*/ } else { Intersect(treeA, treeB); /* if (chainContactInfo.atomContactHash.Count == 0) * { * chainContactInfo = null; * return null; * }*/ } return(chainContactInfo); }
/// <summary> /// return the overlapped leaf nodes /// </summary> /// <param name="extTree"></param> /// <returns></returns> public void Intersect(BVTree treeA, BVTree treeB) { if (treeA == null || treeB == null) { return; } if (!treeA.Root.BoundBox.MayOverlap (treeB.Root.BoundBox, AppSettings.parameters.contactParams.cutoffAtomDist)) { return; } // if it is a leaf node of tree A if (treeA.Root.AtomList.Length <= BVTree.leafAtomNumber) { // it is a leaf node of tree B if (treeB.Root.AtomList.Length <= BVTree.leafAtomNumber) { foreach (AtomInfo atomA in treeA.Root.AtomList) { foreach (AtomInfo atomB in treeB.Root.AtomList) { float atomDist = (float)(atomA - atomB); // if distance is less than the threshold // possible atomic contact if (atomDist < AppSettings.parameters.contactParams.cutoffAtomDist) { AtomPair atomPair = new AtomPair(); atomPair.firstAtom = atomA; atomPair.secondAtom = atomB; atomPair.distance = atomDist; string seqIdString = atomA.seqId + "_" + atomB.seqId; if (!chainContactInfo.atomContactHash.ContainsKey(seqIdString)) { chainContactInfo.atomContactHash.Add(seqIdString, atomPair); } else { // give priority to Cbeta then Calpha if (atomPair.firstAtom.atomName == "CB" && atomPair.secondAtom.atomName == "CB") { chainContactInfo.atomContactHash[seqIdString] = atomPair; } } } } } } else { Intersect(treeA, treeB.LeftBranch); Intersect(treeA, treeB.RightBranch); } } else { Intersect(treeA.LeftBranch, treeB); Intersect(treeA.RightBranch, treeB); } }
/// <summary> /// interactive chains from all pairs of 2 asymmetric chains + /// their corresponding translated chains /// </summary> /// <param name="chainASymOp"></param> /// <param name="chainBSymOp"></param> /// <param name="origTreeA">chain A in the center unit cell /// can be a transformed chain from one chain in the asymmetry unit /// after applied symmetry operation </param> /// <param name="origTreeB">chain B in the center unit cell(description same as chain A)</param> private bool DetectChainContactWithTranslation(string chainASymOp, string chainBSymOp, BVTree origTreeA, BVTree origTreeB, bool skipOrig) { // compare center chain A with all translated chain B // translated BVTrees for chain B return(DetectTwoChainContactWithTranslation (chainBSymOp, chainASymOp, origTreeB, origTreeA, skipOrig)); }
/// <summary> /// Detect all interfaces in a crystal /// built from XML coordinates file /// </summary> /// <param name="xmlFile"></param> /// <param name="paramFile"></param> public void FindInteractChains(string xmlFile, Dictionary <int, string> interfaceDefHash) { asymChainTreesHash.Clear(); interfaceChainsList.Clear(); string[] symOpStrings = GetSymOpStrings(interfaceDefHash); if (AppSettings.parameters == null) { AppSettings.LoadParameters(); } pdbId = xmlFile.Substring(xmlFile.LastIndexOf("\\") + 1, 4); CrystalBuilder crystalBuilder = new CrystalBuilder("ALL"); Dictionary <string, AtomInfo[]> interfaceChainHash = null; try { interfaceChainHash = crystalBuilder.BuildCrystal(xmlFile, symOpStrings); } catch (Exception ex) { throw new Exception("Building interface chains errors: " + xmlFile + ". " + ex.Message); } // for each chain in a cell including original and symmetric chains foreach (string chainAndSymOp in interfaceChainHash.Keys) { BVTree chainTree = new BVTree(); chainTree.BuildBVTree(interfaceChainHash[chainAndSymOp], AppSettings.parameters.kDopsParam.bvTreeMethod, true); asymChainTreesHash.Add(chainAndSymOp, chainTree); } ChainContact chainContact = null; List <int> interfaceList = new List <int> (interfaceDefHash.Keys); interfaceList.Sort(); foreach (int interfaceId in interfaceList) { string[] interfaceDefStrings = interfaceDefHash[interfaceId].ToString().Split(';'); chainContact = new ChainContact(interfaceDefStrings[0], interfaceDefStrings[1]); BVTree tree1 = (BVTree)asymChainTreesHash[interfaceDefStrings[0]]; BVTree tree2 = (BVTree)asymChainTreesHash[interfaceDefStrings[1]]; ChainContactInfo contactInfo = chainContact.GetChainContactInfo(tree1, tree2); if (contactInfo == null) { continue; } InterfaceChains interfaceChains = null; interfaceChains = new InterfaceChains(interfaceDefStrings[0], interfaceDefStrings[1], tree1.Root.AtomList, tree2.Root.AtomList); interfaceChains.pdbId = pdbId; interfaceChains.interfaceId = interfaceId; interfaceChains.seqDistHash = contactInfo.GetBbDistHash(); // interfaceChains.seqDistHash = contactInfo.cbetaContactHash; interfaceChains.seqContactHash = contactInfo.GetContactsHash(); interfaceChainsList.Add(interfaceChains); } }
/// <summary> /// get the interface between two input chains /// </summary> /// <param name="chain1"></param> /// <param name="chain2"></param> /// <returns></returns> public ChainContactInfo GetAllChainContactInfo(AtomInfo[] chain1, AtomInfo[] chain2) { BVTree chain1Tree = new BVTree(); chain1Tree.BuildBVTree(chain1, AppSettings.parameters.kDopsParam.bvTreeMethod, true); BVTree chain2Tree = new BVTree(); chain2Tree.BuildBVTree(chain2, AppSettings.parameters.kDopsParam.bvTreeMethod, true); return(GetAnyChainContactInfo(chain1Tree, chain2Tree)); }
/// <summary> /// get interatomic contacts /// </summary> /// <param name="treeA"></param> /// <param name="treeB"></param> /// <returns></returns> public ChainContactInfo GetDomainContactInfo(BVTree treeA, BVTree treeB) { chainContactInfo = new ChainContactInfo(); Intersect(treeA, treeB); if (chainContactInfo.atomContactHash.Count < AppSettings.parameters.contactParams.domainNumOfAtomContacts) { chainContactInfo = null; return(null); } return(chainContactInfo); }
/// <summary> /// build BVtrees for chains in a biological unit /// </summary> /// <param name="biolUnit"></param> /// <returns></returns> private Dictionary <string, BVTree> BuildBVtreesForAsu(Dictionary <string, AtomInfo[]> asymUnit) { Dictionary <string, BVTree> chainTreesHash = new Dictionary <string, BVTree> (); // for each chain in the biological unit // build BVtree foreach (string chainAndSymOp in asymUnit.Keys) { BVTree chainTree = new BVTree(); chainTree.BuildBVTree(asymUnit[chainAndSymOp], AppSettings.parameters.kDopsParam.bvTreeMethod, true); chainTreesHash.Add(chainAndSymOp, chainTree); } return(chainTreesHash); }
/// <summary> /// read only /// if two input chains overlap or not /// </summary> public bool IsChainInteract(BVTree treeA, BVTree treeB) { chainContactInfo = new ChainContactInfo(); if (BVTree.treeType == '1') { IntersectResidues(treeA, treeB); if (chainContactInfo.atomContactHash.Count < AppSettings.parameters.contactParams.numOfAtomContacts || chainContactInfo.cbetaContactHash.Count < AppSettings.parameters.contactParams.numOfResidueContacts) { chainContactInfo = null; return(false); } } else { Intersect(treeA, treeB); if (chainContactInfo.atomContactHash.Count < AppSettings.parameters.contactParams.numOfResidueContacts) { chainContactInfo = null; return(false); } } return(true); }
/// <summary> /// get interatomic contacts /// </summary> /// <param name="treeA"></param> /// <param name="treeB"></param> /// <returns></returns> public ChainContactInfo GetChainContactInfo(BVTree treeA, BVTree treeB, double [] translateVectorA) { chainContactInfo = new ChainContactInfo(); if (BVTree.treeType == '1') { IntersectResidues(treeA, treeB, translateVectorA); if (chainContactInfo.atomContactHash.Count < AppSettings.parameters.contactParams.numOfAtomContacts || chainContactInfo.cbetaContactHash.Count < AppSettings.parameters.contactParams.numOfResidueContacts) { chainContactInfo = null; return(null); } } else { Intersect(treeA, treeB, translateVectorA); if (chainContactInfo.atomContactHash.Count < AppSettings.parameters.contactParams.numOfResidueContacts) { chainContactInfo = null; return(null); } } return(chainContactInfo); }
/// <summary> /// Detect all interfaces in a crystal /// built from XML coordinates file /// </summary> /// <param name="xmlFile"></param> /// <param name="paramFile"></param> public int FindInteractChains(string xmlFile) { interChainId = 0; asymChainTreesHash.Clear(); interfaceChainsList.Clear(); pdbId = xmlFile.Substring(xmlFile.LastIndexOf("\\") + 1, 4); CrystalBuilder crystalBuilder = new CrystalBuilder(AppSettings.parameters.contactParams.atomType); Dictionary <string, AtomInfo[]> unitCellChainsHash = null; int[] maxSteps = null; // build crystal based on all symmetry operations try { maxSteps = crystalBuilder.BuildCrystal(xmlFile, ref unitCellChainsHash); if (unitCellChainsHash == null) { throw new Exception("Cannot build unit cell."); } // if there are too many chains in a unit cell // only compute the interfaces between original ASU chains // and other chains if (unitCellChainsHash.Count > unitCellSize) { // throw new Exception ("Unit cell too large. Skip processing " + pdbId + "."); // if there are too many chains in the unit cell, // only check the interfaces within the unit cell. for (int i = 0; i < maxSteps.Length; i++) { maxSteps[i] = 0; } } // if there are too many chains, only use chains in the asymmetric unit if (unitCellChainsHash.Count >= maxUnitCellSize) { Dictionary <string, AtomInfo[]> asuChainHash = GetAsymUnitHash(unitCellChainsHash); unitCellChainsHash = asuChainHash; // if it is still too big due to Non-symmetry operators, // only use chains in the original asu in the PDB file, may not be the real Asu if (asuChainHash.Count >= unitCellSize) { Dictionary <string, AtomInfo[]> origAsuChainHash = GetOrigAsymUnitHash(asuChainHash); unitCellChainsHash = origAsuChainHash; } } } catch (Exception ex) { throw new Exception("Building Crystal Errors: " + xmlFile + " " + ex.Message); } /* * build BVTree for original asymmetric chains + * their chains after applied symmetry operations * without translations */ try { // for each chain in a cell including original and symmetric chains foreach (string chainAndSymOp in unitCellChainsHash.Keys) { BVTree chainTree = new BVTree(); chainTree.BuildBVTree(unitCellChainsHash[chainAndSymOp], AppSettings.parameters.kDopsParam.bvTreeMethod, true); asymChainTreesHash.Add(chainAndSymOp, chainTree); } } catch (Exception ex) { throw new Exception("Building Bounding Volumn Tree Errors: " + xmlFile + ". " + ex.Message); } /* * the critical step for the computation time * check all pairs of chains including translated chains * return a list of atompairs if exists */ cryst1 = crystalBuilder.Crystal1; fract2CartnMatrix = crystalBuilder.fract2cartnMatrix; List <string> chainAndSymOps = new List <string> (asymChainTreesHash.Keys); List <string> orderedChainAndSymOps = SortChainsInUnitCell(chainAndSymOps); // check interactions between 2 different chains and their translated chains // chainAndSymOps: chainId_symmetry operator_full symmetry operations // e.g. A_1_545 try { ComputeUniqueInterfacesFromCryst(orderedChainAndSymOps, maxSteps); } catch (Exception ex) { /* ProtCidSettings.progressInfo.progStrQueue.Enqueue * ("Retrieving unique interfaces of " + pdbId + " errors: " + ex.Message);*/ throw new Exception("Retrieving unique interfaces of " + pdbId + " errors: " + ex.Message); } return(unitCellChainsHash.Count); }
/// <summary> /// Detect all interfaces in a crystal /// built from XML coordinates file /// </summary> /// <param name="xmlFile"></param> /// <param name="paramFile"></param> public void FindInteractChains(string xmlFile, Dictionary <int, string> interfaceDefHash, Dictionary <int, string> interfaceEntityInfoHash, out bool interfaceExist) { interChainId = 0; interfaceChainsList.Clear(); asymChainTreesHash.Clear(); interfaceExist = true; string[] symOpStrings = GetSymOpStrings(interfaceDefHash); pdbId = xmlFile.Substring(xmlFile.LastIndexOf("\\") + 1, 4); CrystalBuilder crystalBuilder = new CrystalBuilder(AppSettings.parameters.contactParams.atomType); Dictionary <string, AtomInfo[]> interfaceChainHash = null; try { interfaceChainHash = crystalBuilder.BuildCrystal(xmlFile, symOpStrings); } catch (Exception ex) { throw new Exception("Building interface chains errors: " + xmlFile + " " + ex.Message); } // for each chain in a cell including original and symmetric chains foreach (string chainAndSymOp in interfaceChainHash.Keys) { BVTree chainTree = new BVTree(); chainTree.BuildBVTree(interfaceChainHash[chainAndSymOp], AppSettings.parameters.kDopsParam.bvTreeMethod, true); asymChainTreesHash.Add(chainAndSymOp, chainTree); } ChainContact chainContact = null; foreach (int interfaceId in interfaceDefHash.Keys) { string[] interfaceDefStrings = interfaceDefHash[interfaceId].ToString().Split(';'); chainContact = new ChainContact(interfaceDefStrings[0], interfaceDefStrings[1]); BVTree tree1 = (BVTree)asymChainTreesHash[interfaceDefStrings[0]]; BVTree tree2 = (BVTree)asymChainTreesHash[interfaceDefStrings[1]]; if (tree1 == null || tree2 == null) { continue; } ChainContactInfo contactInfo = chainContact.GetChainContactInfo(tree1, tree2); if (contactInfo == null) { interfaceExist = false; break; } string[] entityStrings = interfaceEntityInfoHash[interfaceId].Split('_'); InterfaceChains interfaceChains = null; interfaceChains = new InterfaceChains(interfaceDefStrings[0], interfaceDefStrings[1], tree1.Root.CalphaCbetaAtoms(), tree2.Root.CalphaCbetaAtoms()); // interfaceChains = new InterfaceChains (symOpStrings[0], symOpStrings[1], tree1.Root.AtomList, tree2.Root.AtomList); interfaceChains.pdbId = pdbId; interfaceChains.interfaceId = interfaceId; interfaceChains.entityId1 = Convert.ToInt32(entityStrings[0]); interfaceChains.entityId2 = Convert.ToInt32(entityStrings[1]); interfaceChains.seqDistHash = contactInfo.GetBbDistHash(); interfaceChains.seqContactHash = contactInfo.GetContactsHash(); interfaceChainsList.Add(interfaceChains); } }
/// <summary> /// check interaction a center chain with /// another translated chains /// </summary> /// <param name="chainASymOp">translated chain</param> /// <param name="chainBSymOp">center chain</param> /// <param name="treeA">translated tree</param> /// <param name="treeB">center tree</param> private bool DetectTwoChainContactWithTranslation(string translateChainSymOp, string centerChainSymOp, BVTree translateTree, BVTree centerTree, bool skipOrig) { string transChainSymOp = ""; int origX = -1; int origY = -1; int origZ = -1; string transVectString = translateChainSymOp.Substring(translateChainSymOp.LastIndexOf("_") + 1, translateChainSymOp.Length - translateChainSymOp.LastIndexOf("_") - 1); GetOrigPoint(transVectString, ref origX, ref origY, ref origZ); bool thisNewInterfaceExist = false; try { ChainContact chainContact = null; /* total number of BVTrees: (step * 2 + 1) ^ 3 * total comparison of BVTrees: (step * 2 + 1) - 1 * e.g. steps = 1; total comparisons: 26 * compare all neighbor unit cells with the original unit cell */ // comare center chain B with center chain A and all its translated chains foreach (int xA in xStepList) { foreach (int yA in yStepList) { foreach (int zA in zStepList) { if (xA == 0 && yA == 0 && zA == 0 && skipOrig) { thisNewInterfaceExist = true; continue; } // chain_symmetry operator number_translation vector // e.g. A_1_556 (chain A, symmetry operator is 1, and translation vector (0, 0, 1)) transChainSymOp = GetTransSymOpString(translateChainSymOp, origX + xA, origY + yA, origZ + zA); if (parsedPairList.Contains(centerChainSymOp + "_" + transChainSymOp)) { continue; } else { parsedPairList.Add(centerChainSymOp + "_" + transChainSymOp); } double[] fractVectorA = new double [3] { xA, yA, zA }; double[] translateVectorA = ComputeTransVectInCartn(fractVectorA); // BVTree translatedTree = translateTree.UpdateBVTree (translateVectorA); chainContact = new ChainContact(centerChainSymOp, transChainSymOp); ChainContactInfo contactInfo = chainContact.GetChainContactInfo(centerTree, translateTree, translateVectorA); if (contactInfo != null) { InterfaceChains interfaceChains = null; Node updateRoot = new Node(translateTree.Root.CalphaCbetaAtoms()); updateRoot.UpdateLeafNode(translateVectorA); interfaceChains = new InterfaceChains(centerChainSymOp, transChainSymOp, centerTree.Root.CalphaCbetaAtoms(), updateRoot.AtomList); interfaceChains.seqDistHash = contactInfo.GetBbDistHash(); interfaceChains.seqContactHash = contactInfo.GetContactsHash(); if (AddInterChainsToList(interfaceChains)) { thisNewInterfaceExist = true; } } } } } // end of translated chain } catch (Exception ex) { throw new Exception(string.Format("Comparisons of rotated and translated chains ({0}, {1}) errors: {2}", translateChainSymOp, centerChainSymOp, ex.Message)); } return(thisNewInterfaceExist); }
/// <summary> /// check interaction a center chain with /// its corresponding translated chains /// </summary> /// <param name="chainASymOp"></param> /// <param name="chainBSymOp"></param> /// <param name="treeA"></param> /// <param name="treeB"></param> private bool DetectChainContactWithTranslation(string chainASymOp, BVTree origTreeA) { string transChainASymOp = ""; List <string> parsedSymOpsList = new List <string> (); int origX = -1; int origY = -1; int origZ = -1; string transVectString = chainASymOp.Substring(chainASymOp.LastIndexOf("_") + 1, chainASymOp.Length - chainASymOp.LastIndexOf("_") - 1); GetOrigPoint(transVectString, ref origX, ref origY, ref origZ); bool thisNewInterfaceExist = false; /* total number of BVTrees: (step * 2 + 1) ^ 3 * total comparison of BVTrees: (step * 2 + 1) - 1 * e.g. steps = 1; total comparisons: 26 * compare all neighbor unit cells with the original unit cell */ // compare a center chain with all its corresponding translated chains foreach (int xA in xStepList) { foreach (int yA in yStepList) { foreach (int zA in zStepList) { // skip check contacts with itself // probably we need to check contacts within a chain also. if (xA == 0 && yA == 0 && zA == 0) { thisNewInterfaceExist = true; continue; } transChainASymOp = GetTransSymOpString(chainASymOp, origX + xA, origY + yA, origZ + zA); if (parsedPairList.Contains(chainASymOp + "_" + transChainASymOp)) { continue; } else { parsedPairList.Add(chainASymOp + "_" + transChainASymOp); } double[] fractVectorA = new double [3] { xA, yA, zA }; double[] translateVectorA = ComputeTransVectInCartn(fractVectorA); // get the BVtree for the translated chain // BVTree treeA = origTreeA.UpdateBVTree (translateVectorA); ChainContact chainContact = new ChainContact(chainASymOp, transChainASymOp); ChainContactInfo contactInfo = chainContact.GetChainContactInfo(origTreeA, origTreeA, translateVectorA); if (contactInfo != null) { InterfaceChains interfaceChains = null; Node updateRoot = new Node(origTreeA.Root.CalphaCbetaAtoms()); updateRoot.UpdateLeafNode(translateVectorA); interfaceChains = new InterfaceChains(chainASymOp, transChainASymOp, origTreeA.Root.CalphaCbetaAtoms(), updateRoot.AtomList); interfaceChains.seqDistHash = contactInfo.GetBbDistHash(); interfaceChains.seqContactHash = contactInfo.GetContactsHash(); if (AddInterChainsToList(interfaceChains)) { thisNewInterfaceExist = true; } } } } } return(thisNewInterfaceExist); }
/// <summary> /// Initializes a new instance of the <see cref="NavMeshBuilder" /> class. /// Add all the PolyMesh and PolyMeshDetail attributes to the Navigation Mesh. /// Then, add Off-Mesh connection support. /// </summary> /// <param name="polyMesh">The PolyMesh</param> /// <param name="polyMeshDetail">The PolyMeshDetail</param> /// <param name="offMeshCons">Offmesh connection data</param> /// <param name="settings">The settings used to build.</param> public NavMeshBuilder(PolyMesh polyMesh, PolyMeshDetail polyMeshDetail, OffMeshConnection[] offMeshCons, NavMeshGenerationSettings settings) { if (settings.VertsPerPoly > PathfindingCommon.VERTS_PER_POLYGON) { throw new InvalidOperationException("The number of vertices per polygon is above SharpNav's limit"); } if (polyMesh.VertCount == 0) { throw new InvalidOperationException("The provided PolyMesh has no vertices."); } if (polyMesh.PolyCount == 0) { throw new InvalidOperationException("The provided PolyMesh has not polys."); } int nvp = settings.VertsPerPoly; //classify off-mesh connection points BoundarySide[] offMeshSides = new BoundarySide[offMeshCons.Length * 2]; int storedOffMeshConCount = 0; int offMeshConLinkCount = 0; if (offMeshCons.Length > 0) { //find height bounds float hmin = float.MaxValue; float hmax = -float.MaxValue; if (polyMeshDetail != null) { for (int i = 0; i < polyMeshDetail.VertCount; i++) { float h = polyMeshDetail.Verts[i].Y; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } else { for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; float h = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } hmin -= settings.MaxClimb; hmax += settings.MaxClimb; BBox3 bounds = polyMesh.Bounds; bounds.Min.Y = hmin; bounds.Max.Y = hmax; for (int i = 0; i < offMeshCons.Length; i++) { Vector3 p0 = offMeshCons[i].Pos0; Vector3 p1 = offMeshCons[i].Pos1; offMeshSides[i * 2 + 0] = BoundarySideExtensions.FromPoint(p0, bounds); offMeshSides[i * 2 + 1] = BoundarySideExtensions.FromPoint(p1, bounds); //off-mesh start position isn't touching mesh if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { if (p0.Y < bounds.Min.Y || p0.Y > bounds.Max.Y) { offMeshSides[i * 2 + 0] = 0; } } //count number of links to allocate if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { offMeshConLinkCount++; } if (offMeshSides[i * 2 + 1] == BoundarySide.Internal) { offMeshConLinkCount++; } if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { storedOffMeshConCount++; } } } //off-mesh connections stored as polygons, adjust values int totPolyCount = polyMesh.PolyCount + storedOffMeshConCount; int totVertCount = polyMesh.VertCount + storedOffMeshConCount * 2; //find portal edges int edgeCount = 0; int portalCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { PolyMesh.Polygon p = polyMesh.Polys[i]; for (int j = 0; j < nvp; j++) { if (p.Vertices[j] == PolyMesh.NullId) { break; } edgeCount++; if (PolyMesh.IsBoundaryEdge(p.NeighborEdges[j])) { int dir = p.NeighborEdges[j] % 16; if (dir != 15) { portalCount++; } } } } int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2; //find unique detail vertices int uniqueDetailVertCount = 0; int detailTriCount = 0; if (polyMeshDetail != null) { detailTriCount = polyMeshDetail.TrisCount; for (int i = 0; i < polyMesh.PolyCount; i++) { int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numDetailVerts - numPolyVerts; } } else { uniqueDetailVertCount = 0; detailTriCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numPolyVerts - 2; } } //allocate data header = new PathfindingCommon.NavMeshInfo(); navVerts = new Vector3[totVertCount]; navPolys = new Poly[totPolyCount]; navDMeshes = new PolyMeshDetail.MeshData[polyMesh.PolyCount]; navDVerts = new Vector3[uniqueDetailVertCount]; navDTris = new PolyMeshDetail.TriangleData[detailTriCount]; offMeshConnections = new OffMeshConnection[storedOffMeshConCount]; //store header //HACK TiledNavMesh should figure out the X/Y/layer instead of the user maybe? header.X = 0; header.Y = 0; header.Layer = 0; header.PolyCount = totPolyCount; header.VertCount = totVertCount; header.MaxLinkCount = maxLinkCount; header.Bounds = polyMesh.Bounds; header.DetailMeshCount = polyMesh.PolyCount; header.DetailVertCount = uniqueDetailVertCount; header.DetailTriCount = detailTriCount; header.OffMeshBase = polyMesh.PolyCount; header.WalkableHeight = settings.AgentHeight; header.WalkableRadius = settings.AgentRadius; header.WalkableClimb = settings.MaxClimb; header.OffMeshConCount = storedOffMeshConCount; header.BvNodeCount = settings.BuildBoundingVolumeTree ? polyMesh.PolyCount * 2 : 0; header.BvQuantFactor = 1f / settings.CellSize; int offMeshVertsBase = polyMesh.VertCount; int offMeshPolyBase = polyMesh.PolyCount; //store vertices for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; navVerts[i].X = polyMesh.Bounds.Min.X + iv.X * settings.CellSize; navVerts[i].Y = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; navVerts[i].Z = polyMesh.Bounds.Min.Z + iv.Z * settings.CellSize; } //off-mesh link vertices int n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navVerts[offMeshVertsBase + (n * 2 + 0)] = offMeshCons[i].Pos0; navVerts[offMeshVertsBase + (n * 2 + 1)] = offMeshCons[i].Pos1; n++; } } //store polygons for (int i = 0; i < polyMesh.PolyCount; i++) { navPolys[i] = new Poly(); navPolys[i].VertCount = 0; navPolys[i].Tag = polyMesh.Polys[i].Tag; navPolys[i].Area = polyMesh.Polys[i].Area; navPolys[i].PolyType = PolygonType.Ground; navPolys[i].Verts = new int[nvp]; navPolys[i].Neis = new int[nvp]; for (int j = 0; j < nvp; j++) { if (polyMesh.Polys[i].Vertices[j] == PolyMesh.NullId) { break; } navPolys[i].Verts[j] = polyMesh.Polys[i].Vertices[j]; if (PolyMesh.IsBoundaryEdge(polyMesh.Polys[i].NeighborEdges[j])) { //border or portal edge int dir = polyMesh.Polys[i].NeighborEdges[j] % 16; if (dir == 0xf) //border { navPolys[i].Neis[j] = 0; } else if (dir == 0) //portal x- { navPolys[i].Neis[j] = Link.External | 4; } else if (dir == 1) //portal z+ { navPolys[i].Neis[j] = Link.External | 2; } else if (dir == 2) //portal x+ { navPolys[i].Neis[j] = Link.External | 0; } else if (dir == 3) //portal z- { navPolys[i].Neis[j] = Link.External | 6; } } else { //normal connection navPolys[i].Neis[j] = polyMesh.Polys[i].NeighborEdges[j] + 1; } navPolys[i].VertCount++; } } //off-mesh connection vertices n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navPolys[offMeshPolyBase + n].VertCount = 2; navPolys[offMeshPolyBase + n].Verts = new int[nvp]; navPolys[offMeshPolyBase + n].Verts[0] = offMeshVertsBase + (n * 2 + 0); navPolys[offMeshPolyBase + n].Verts[1] = offMeshVertsBase + (n * 2 + 1); navPolys[offMeshPolyBase + n].Tag = offMeshCons[i].Flags; navPolys[offMeshPolyBase + n].Area = polyMesh.Polys[offMeshCons[i].Poly].Area; //HACK is this correct? navPolys[offMeshPolyBase + n].PolyType = PolygonType.OffMeshConnection; n++; } } //store detail meshes and vertices if (polyMeshDetail != null) { int vbase = 0; List <Vector3> storedDetailVerts = new List <Vector3>(); for (int i = 0; i < polyMesh.PolyCount; i++) { int vb = polyMeshDetail.Meshes[i].VertexIndex; int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = vbase; navDMeshes[i].VertexCount = numDetailVerts - numPolyVerts; navDMeshes[i].TriangleIndex = polyMeshDetail.Meshes[i].TriangleIndex; navDMeshes[i].TriangleCount = polyMeshDetail.Meshes[i].TriangleCount; //Copy detail vertices //first 'nv' verts are equal to nav poly verts //the rest are detail verts for (int j = 0; j < navDMeshes[i].VertexCount; j++) { storedDetailVerts.Add(polyMeshDetail.Verts[vb + numPolyVerts + j]); } vbase += numDetailVerts - numPolyVerts; } navDVerts = storedDetailVerts.ToArray(); //store triangles for (int j = 0; j < polyMeshDetail.TrisCount; j++) { navDTris[j] = polyMeshDetail.Tris[j]; } } else { //create dummy detail mesh by triangulating polys int tbase = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = 0; navDMeshes[i].VertexCount = 0; navDMeshes[i].TriangleIndex = tbase; navDMeshes[i].TriangleCount = numPolyVerts - 2; //triangulate polygon for (int j = 2; j < numPolyVerts; j++) { navDTris[tbase].VertexHash0 = 0; navDTris[tbase].VertexHash1 = j - 1; navDTris[tbase].VertexHash2 = j; //bit for each edge that belongs to the poly boundary navDTris[tbase].Flags = 1 << 2; if (j == 2) { navDTris[tbase].Flags |= 1 << 0; } if (j == numPolyVerts - 1) { navDTris[tbase].Flags |= 1 << 4; } tbase++; } } } //store and create BV tree if (settings.BuildBoundingVolumeTree) { //build tree navBvTree = new BVTree(polyMesh.Verts, polyMesh.Polys, nvp, settings.CellSize, settings.CellHeight); } //store off-mesh connections n = 0; for (int i = 0; i < offMeshConnections.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { offMeshConnections[n].Poly = offMeshPolyBase + n; //copy connection end points offMeshConnections[n].Pos0 = offMeshCons[i].Pos0; offMeshConnections[n].Pos1 = offMeshCons[i].Pos1; offMeshConnections[n].Radius = offMeshCons[i].Radius; offMeshConnections[n].Flags = offMeshCons[i].Flags; offMeshConnections[n].Side = offMeshSides[i * 2 + 1]; offMeshConnections[n].Tag = offMeshCons[i].Tag; n++; } } }
/// <summary> /// Initializes a new instance of the <see cref="NavMeshBuilder" /> class. /// Add all the PolyMesh and PolyMeshDetail attributes to the Navigation Mesh. /// Then, add Off-Mesh connection support. /// </summary> /// <param name="polyMesh">The PolyMesh</param> /// <param name="polyMeshDetail">The PolyMeshDetail</param> /// <param name="offMeshCons">Offmesh connection data</param> /// <param name="settings">The settings used to build.</param> public NavMeshBuilder(PolyMesh polyMesh, PolyMeshDetail polyMeshDetail, OffMeshConnection[] offMeshCons, NavMeshGenerationSettings settings) { if (settings.VertsPerPoly > PathfindingCommon.VERTS_PER_POLYGON) throw new InvalidOperationException("The number of vertices per polygon is above SharpNav's limit"); if (polyMesh.VertCount == 0) throw new InvalidOperationException("The provided PolyMesh has no vertices."); if (polyMesh.PolyCount == 0) throw new InvalidOperationException("The provided PolyMesh has not polys."); int nvp = settings.VertsPerPoly; //classify off-mesh connection points BoundarySide[] offMeshSides = new BoundarySide[offMeshCons.Length * 2]; int storedOffMeshConCount = 0; int offMeshConLinkCount = 0; if (offMeshCons.Length > 0) { //find height bounds float hmin = float.MaxValue; float hmax = -float.MaxValue; if (polyMeshDetail != null) { for (int i = 0; i < polyMeshDetail.VertCount; i++) { float h = polyMeshDetail.Verts[i].Y; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } else { for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; float h = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } hmin -= settings.MaxClimb; hmax += settings.MaxClimb; BBox3 bounds = polyMesh.Bounds; bounds.Min.Y = hmin; bounds.Max.Y = hmax; for (int i = 0; i < offMeshCons.Length; i++) { Vector3 p0 = offMeshCons[i].Pos0; Vector3 p1 = offMeshCons[i].Pos1; offMeshSides[i * 2 + 0] = BoundarySideExtensions.FromPoint(p0, bounds); offMeshSides[i * 2 + 1] = BoundarySideExtensions.FromPoint(p1, bounds); //off-mesh start position isn't touching mesh if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { if (p0.Y < bounds.Min.Y || p0.Y > bounds.Max.Y) offMeshSides[i * 2 + 0] = 0; } //count number of links to allocate if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) offMeshConLinkCount++; if (offMeshSides[i * 2 + 1] == BoundarySide.Internal) offMeshConLinkCount++; if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) storedOffMeshConCount++; } } //off-mesh connections stored as polygons, adjust values int totPolyCount = polyMesh.PolyCount + storedOffMeshConCount; int totVertCount = polyMesh.VertCount + storedOffMeshConCount * 2; //find portal edges int edgeCount = 0; int portalCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { PolyMesh.Polygon p = polyMesh.Polys[i]; for (int j = 0; j < nvp; j++) { if (p.Vertices[j] == PolyMesh.NullId) break; edgeCount++; if (PolyMesh.IsBoundaryEdge(p.NeighborEdges[j])) { int dir = p.NeighborEdges[j] % 16; if (dir != 15) portalCount++; } } } int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2; //find unique detail vertices int uniqueDetailVertCount = 0; int detailTriCount = 0; if (polyMeshDetail != null) { detailTriCount = polyMeshDetail.TrisCount; for (int i = 0; i < polyMesh.PolyCount; i++) { int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numDetailVerts - numPolyVerts; } } else { uniqueDetailVertCount = 0; detailTriCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numPolyVerts - 2; } } //allocate data header = new PathfindingCommon.NavMeshInfo(); navVerts = new Vector3[totVertCount]; navPolys = new Poly[totPolyCount]; navDMeshes = new PolyMeshDetail.MeshData[polyMesh.PolyCount]; navDVerts = new Vector3[uniqueDetailVertCount]; navDTris = new PolyMeshDetail.TriangleData[detailTriCount]; offMeshConnections = new OffMeshConnection[storedOffMeshConCount]; //store header //HACK TiledNavMesh should figure out the X/Y/layer instead of the user maybe? header.X = 0; header.Y = 0; header.Layer = 0; header.PolyCount = totPolyCount; header.VertCount = totVertCount; header.MaxLinkCount = maxLinkCount; header.Bounds = polyMesh.Bounds; header.DetailMeshCount = polyMesh.PolyCount; header.DetailVertCount = uniqueDetailVertCount; header.DetailTriCount = detailTriCount; header.OffMeshBase = polyMesh.PolyCount; header.WalkableHeight = settings.AgentHeight; header.WalkableRadius = settings.AgentRadius; header.WalkableClimb = settings.MaxClimb; header.OffMeshConCount = storedOffMeshConCount; header.BvNodeCount = settings.BuildBoundingVolumeTree ? polyMesh.PolyCount * 2 : 0; header.BvQuantFactor = 1f / settings.CellSize; int offMeshVertsBase = polyMesh.VertCount; int offMeshPolyBase = polyMesh.PolyCount; //store vertices for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; navVerts[i].X = polyMesh.Bounds.Min.X + iv.X * settings.CellSize; navVerts[i].Y = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; navVerts[i].Z = polyMesh.Bounds.Min.Z + iv.Z * settings.CellSize; } //off-mesh link vertices int n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navVerts[offMeshVertsBase + (n * 2 + 0)] = offMeshCons[i].Pos0; navVerts[offMeshVertsBase + (n * 2 + 1)] = offMeshCons[i].Pos1; n++; } } //store polygons for (int i = 0; i < polyMesh.PolyCount; i++) { navPolys[i] = new Poly(); navPolys[i].VertCount = 0; navPolys[i].Tag = polyMesh.Polys[i].Tag; navPolys[i].Area = polyMesh.Polys[i].Area; navPolys[i].PolyType = PolygonType.Ground; navPolys[i].Verts = new int[nvp]; navPolys[i].Neis = new int[nvp]; for (int j = 0; j < nvp; j++) { if (polyMesh.Polys[i].Vertices[j] == PolyMesh.NullId) break; navPolys[i].Verts[j] = polyMesh.Polys[i].Vertices[j]; if (PolyMesh.IsBoundaryEdge(polyMesh.Polys[i].NeighborEdges[j])) { //border or portal edge int dir = polyMesh.Polys[i].NeighborEdges[j] % 16; if (dir == 0xf) //border navPolys[i].Neis[j] = 0; else if (dir == 0) //portal x- navPolys[i].Neis[j] = Link.External | 4; else if (dir == 1) //portal z+ navPolys[i].Neis[j] = Link.External | 2; else if (dir == 2) //portal x+ navPolys[i].Neis[j] = Link.External | 0; else if (dir == 3) //portal z- navPolys[i].Neis[j] = Link.External | 6; } else { //normal connection navPolys[i].Neis[j] = polyMesh.Polys[i].NeighborEdges[j] + 1; } navPolys[i].VertCount++; } } //off-mesh connection vertices n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navPolys[offMeshPolyBase + n].VertCount = 2; navPolys[offMeshPolyBase + n].Verts = new int[nvp]; navPolys[offMeshPolyBase + n].Verts[0] = offMeshVertsBase + (n * 2 + 0); navPolys[offMeshPolyBase + n].Verts[1] = offMeshVertsBase + (n * 2 + 1); navPolys[offMeshPolyBase + n].Tag = offMeshCons[i].Flags; navPolys[offMeshPolyBase + n].Area = polyMesh.Polys[offMeshCons[i].Poly].Area; //HACK is this correct? navPolys[offMeshPolyBase + n].PolyType = PolygonType.OffMeshConnection; n++; } } //store detail meshes and vertices if (polyMeshDetail != null) { int vbase = 0; List<Vector3> storedDetailVerts = new List<Vector3>(); for (int i = 0; i < polyMesh.PolyCount; i++) { int vb = polyMeshDetail.Meshes[i].VertexIndex; int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = vbase; navDMeshes[i].VertexCount = numDetailVerts - numPolyVerts; navDMeshes[i].TriangleIndex = polyMeshDetail.Meshes[i].TriangleIndex; navDMeshes[i].TriangleCount = polyMeshDetail.Meshes[i].TriangleCount; //Copy detail vertices //first 'nv' verts are equal to nav poly verts //the rest are detail verts for (int j = 0; j < navDMeshes[i].VertexCount; j++) { storedDetailVerts.Add(polyMeshDetail.Verts[vb + numPolyVerts + j]); } vbase += numDetailVerts - numPolyVerts; } navDVerts = storedDetailVerts.ToArray(); //store triangles for (int j = 0; j < polyMeshDetail.TrisCount; j++) navDTris[j] = polyMeshDetail.Tris[j]; } else { //create dummy detail mesh by triangulating polys int tbase = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = 0; navDMeshes[i].VertexCount = 0; navDMeshes[i].TriangleIndex = tbase; navDMeshes[i].TriangleCount = numPolyVerts - 2; //triangulate polygon for (int j = 2; j < numPolyVerts; j++) { navDTris[tbase].VertexHash0 = 0; navDTris[tbase].VertexHash1 = j - 1; navDTris[tbase].VertexHash2 = j; //bit for each edge that belongs to the poly boundary navDTris[tbase].Flags = 1 << 2; if (j == 2) navDTris[tbase].Flags |= 1 << 0; if (j == numPolyVerts - 1) navDTris[tbase].Flags |= 1 << 4; tbase++; } } } //store and create BV tree if (settings.BuildBoundingVolumeTree) { //build tree navBvTree = new BVTree(polyMesh.Verts, polyMesh.Polys, nvp, settings.CellSize, settings.CellHeight); } //store off-mesh connections n = 0; for (int i = 0; i < offMeshConnections.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { offMeshConnections[n].Poly = offMeshPolyBase + n; //copy connection end points offMeshConnections[n].Pos0 = offMeshCons[i].Pos0; offMeshConnections[n].Pos1 = offMeshCons[i].Pos1; offMeshConnections[n].Radius = offMeshCons[i].Radius; offMeshConnections[n].Flags = offMeshCons[i].Flags; offMeshConnections[n].Side = offMeshSides[i * 2 + 1]; offMeshConnections[n].Tag = offMeshCons[i].Tag; n++; } } }
/// <summary> /// Contacts between two chains /// update the treeB when it is necessary /// </summary> /// <param name="treeA">center tree</param> /// <param name="treeB">the tree need to be translated by translateVectorA</param> /// <param name="translateVectorA"></param> public void Intersect(BVTree treeA, BVTree treeB, double[] translateVectorA) { if (treeA == null || treeB == null) { return; } BoundingBox bb = treeB.Root.BoundBox.UpdateBoundingBox(translateVectorA); if (!treeA.Root.BoundBox.MayOverlap(bb, AppSettings.parameters.contactParams.cutoffAtomDist)) { return; } // if it is a leaf node of tree A if (treeA.LeftBranch == null && treeA.RightBranch == null) { // it is a leaf node of tree B if (treeB.LeftBranch == null && treeB.RightBranch == null) { Node leafNode = new Node(treeB.Root); leafNode.UpdateLeafNode(translateVectorA); foreach (AtomInfo atomA in treeA.Root.AtomList) { foreach (AtomInfo atomB in leafNode.AtomList) { float atomDist = (float)(atomA - atomB); // if distance is less than the threshold // possible atomic contact if (atomDist < AppSettings.parameters.contactParams.cutoffAtomDist) { AtomPair atomPair = new AtomPair(); atomPair.firstAtom = atomA; atomPair.secondAtom = atomB; atomPair.distance = atomDist; string seqIdString = atomA.seqId + "_" + atomB.seqId; if (!chainContactInfo.atomContactHash.ContainsKey(seqIdString)) { chainContactInfo.atomContactHash.Add(seqIdString, atomPair); } else { // give priority to Cbeta if (atomPair.firstAtom.atomName == "CB" && atomPair.secondAtom.atomName == "CB") { chainContactInfo.atomContactHash[seqIdString] = atomPair; } } } } } } else { Intersect(treeA, treeB.LeftBranch, translateVectorA); Intersect(treeA, treeB.RightBranch, translateVectorA); } } else { Intersect(treeA.LeftBranch, treeB, translateVectorA); Intersect(treeA.RightBranch, treeB, translateVectorA); } }
/// <summary> /// Contacts between two chains /// update the treeB when it is necessary /// </summary> /// <param name="treeA">center tree</param> /// <param name="treeB">the tree need to be translated by translateVectorA</param> /// <param name="translateVectorA"></param> private void IntersectResidues(BVTree treeA, BVTree treeB) { if (!treeA.Root.BoundBox.MayOverlap (treeB.Root.BoundBox, AppSettings.parameters.contactParams.cutoffResidueDist)) { return; } // if it is a leaf node of tree A if (treeA.LeftBranch == null && treeA.RightBranch == null) { // it is a leaf node of tree B if (treeB.LeftBranch == null && treeB.RightBranch == null) { foreach (AtomInfo atomA in treeA.Root.AtomList) { foreach (AtomInfo atomB in treeB.Root.AtomList) { float atomDist = (float)(atomA - atomB); if ((atomA.atomName == "CB" || atomA.atomName == "CA") && (atomB.atomName == "CB" || atomB.atomName == "CA") && atomDist < AppSettings.parameters.contactParams.cutoffResidueDist) { AtomPair atomPair = new AtomPair(); atomPair.firstAtom = atomA; atomPair.secondAtom = atomB; atomPair.distance = atomDist; string seqIdString = atomA.seqId + "_" + atomB.seqId; if (!chainContactInfo.cbetaContactHash.ContainsKey(seqIdString)) { chainContactInfo.cbetaContactHash.Add(seqIdString, atomPair); } else { // give priority to Cbeta then Calpha /* if (atomPair.firstAtom.atomName == "CB" && atomPair.secondAtom.atomName == "CB") * { * chainContactInfo.cbetaContactHash[seqIdString] = atomPair; * }*/ // minimum distance between bb atoms double dist = ((AtomPair)chainContactInfo.cbetaContactHash[seqIdString]).distance; if (dist > atomDist) { chainContactInfo.cbetaContactHash[seqIdString] = atomPair; } } } // if distance is less than the threshold // atomic contact if (atomDist < AppSettings.parameters.contactParams.cutoffAtomDist) { string seqIdString = atomA.seqId + "_" + atomB.seqId; if (!chainContactInfo.atomContactHash.ContainsKey(seqIdString)) { AtomPair atomPair = new AtomPair(); atomPair.firstAtom = atomA; atomPair.secondAtom = atomB; atomPair.distance = atomDist; chainContactInfo.atomContactHash.Add(seqIdString, atomPair); } else { // find the atom pair with minimum distance AtomPair atomPair = (AtomPair)chainContactInfo.atomContactHash[seqIdString]; if (atomDist < atomPair.distance) { atomPair.firstAtom = atomA; atomPair.secondAtom = atomB; atomPair.distance = atomDist; chainContactInfo.atomContactHash[seqIdString] = atomPair; } } } } } } else { IntersectResidues(treeA, treeB.LeftBranch); IntersectResidues(treeA, treeB.RightBranch); } } else { IntersectResidues(treeA.LeftBranch, treeB); IntersectResidues(treeA.RightBranch, treeB); } }