void CornerBasedSplit(SpatialNode node, ISpatialObject obj, out SpatialNode splitNode) { foreach (var cornerChildren in m_CornerSplitChildren) { cornerChildren.Clear(); } // separate children by their closest corners m_CornerSplitChildren[FindClosestCornerIndex(node, obj)].Add(obj); foreach (var child in node.children) { m_CornerSplitChildren[FindClosestCornerIndex(node, child)].Add(child); } // split children by closest corners along each axis var splitLowX = new List <ISpatialObject>(); var splitHighX = new List <ISpatialObject>(); var splitLowY = new List <ISpatialObject>(); var splitHighY = new List <ISpatialObject>(); var splitLowZ = new List <ISpatialObject>(); var splitHighZ = new List <ISpatialObject>(); for (var i = 0; i < m_CornerSplitChildren.Length; ++i) { ((i >> 2) % 2 > 0 ? splitHighX : splitLowX).AddRange(m_CornerSplitChildren[i]); ((i >> 1) % 2 > 0 ? splitHighY : splitLowY).AddRange(m_CornerSplitChildren[i]); (i % 2 > 0 ? splitHighZ : splitLowZ).AddRange(m_CornerSplitChildren[i]); } // find the diff between the split node child counts var dx = Mathf.Abs(splitLowX.Count - splitHighX.Count); var dy = Mathf.Abs(splitLowY.Count - splitHighY.Count); var dz = Mathf.Abs(splitLowZ.Count - splitHighZ.Count); var bestSplits = new List <int>(); if (dx <= dy && dx <= dz) { bestSplits.Add(k_X); } if (dy <= dx && dy <= dz) { bestSplits.Add(k_Y); } if (dz <= dx && dz <= dy) { bestSplits.Add(k_Z); } // handle tiebreakers if multiple equal counts var bestSplit = 0; if (bestSplits.Count == 1) { bestSplit = bestSplits[0]; } else { var overlaps = new List <float>(); var volumes = new List <float>(); foreach (var axis in bestSplits) { // tiebreaker uses overlaps and volume totals switch (axis) { case k_X: CalculateMinMax(splitLowX, out var minLowX, out var maxLowX); CalculateMinMax(splitHighX, out var minHighX, out var maxHighX); overlaps.Add(CalculateOverlap(minLowX, maxLowX, minHighX, maxHighX)); volumes.Add(CalculateVolume(minLowX, maxLowX) + CalculateVolume(minHighX, maxHighX)); break; case k_Y: CalculateMinMax(splitLowY, out var minLowY, out var maxLowY); CalculateMinMax(splitHighY, out var minHighY, out var maxHighY); overlaps.Add(CalculateOverlap(minLowY, maxLowY, minHighY, maxHighY)); volumes.Add(CalculateVolume(minLowY, maxLowY) + CalculateVolume(minHighY, maxHighY)); break; case k_Z: CalculateMinMax(splitLowZ, out var minLowZ, out var maxLowZ); CalculateMinMax(splitHighZ, out var minHighZ, out var maxHighZ); overlaps.Add(CalculateOverlap(minLowZ, maxLowZ, minHighZ, maxHighZ)); volumes.Add(CalculateVolume(minLowZ, maxLowZ) + CalculateVolume(minHighZ, maxHighZ)); break; } } var bestOverlap = float.MaxValue; var bestVolume = float.MaxValue; for (var i = 0; i < bestSplits.Count; ++i) { // check overlap first, if equal check total volume if (overlaps[i] > bestOverlap || Mathf.Approximately(overlaps[i], bestOverlap) && volumes[i] > bestVolume) { continue; } bestSplit = bestSplits[i]; bestOverlap = overlaps[i]; bestVolume = volumes[i]; } } SpatialNode smallNode = null; SpatialNode bigNode = null; node.children.Clear(); splitNode = new SpatialNode { parent = node.parent }; var nodeComparer = Comparer <ISpatialObject> .Default; var splitNodeComparer = Comparer <ISpatialObject> .Default; switch (bestSplit) { case k_X: node.children.AddRange(splitLowX); nodeComparer = m_NodeComparerX; splitNode.children.AddRange(splitHighX); splitNodeComparer = m_SplitNodeComparerX; break; case k_Y: node.children.AddRange(splitLowY); nodeComparer = m_NodeComparerY; splitNode.children.AddRange(splitHighY); splitNodeComparer = m_SplitNodeComparerY; break; case k_Z: node.children.AddRange(splitLowZ); nodeComparer = m_NodeComparerZ; splitNode.children.AddRange(splitHighZ); splitNodeComparer = m_SplitNodeComparerZ; break; } // if a node has too few children, sort them by distance from split axis and get ready for next step if (node.children.Count < m_MinPerNode && nodeComparer != null) { node.children.Sort(nodeComparer); smallNode = node; bigNode = splitNode; } else if (splitNode.children.Count < m_MinPerNode && splitNodeComparer != null) { splitNode.children.Sort(splitNodeComparer); smallNode = splitNode; bigNode = node; } // move closest children from big node to small node until small node has the minimum amount required if (smallNode != null && bigNode != null) { var amount = m_MinPerNode - smallNode.children.Count; var index = bigNode.children.Count - amount; smallNode.children.AddRange(bigNode.children.GetRange(index, amount)); bigNode.children.RemoveRange(index, amount); } node.AdjustBounds(); splitNode.AdjustBounds(); foreach (var child in splitNode.children) { if (child is SpatialNode childNode) { childNode.parent = splitNode; } } }
static int FindClosestCornerIndex(SpatialNode node, ISpatialObject obj) { return((obj.center.x <= node.center.x ? 0 : k_X) + (obj.center.y <= node.center.y ? 0 : k_Y) + (obj.center.z <= node.center.z ? 0 : k_Z)); }
void SplitNode(SpatialNode node, ISpatialObject obj, out SpatialNode splitNode) { CornerBasedSplit(node, obj, out splitNode); }