in GraphLayout/MSAGL/Core/Geometry/OverlapRemoval/OverlapRemovalCluster.cs [721:901]
void GenerateFromEvents(Solver solver, OverlapRemovalParameters parameters,
List<Event> events, bool isHorizontal)
{
// First, sort the events on the perpendicular coordinate of the event
// (e.g. for horizontal constraint generation, order on vertical position).
events.Sort();
#if VERBOSE
System.Diagnostics.Debug.WriteLine("Events:");
foreach (Event evt in events)
{
System.Diagnostics.Debug.WriteLine(" {0}", evt);
}
#endif // VERBOSE
var scanLine = new ScanLine();
foreach (Event evt in events)
{
OverlapRemovalNode currentNode = evt.Node;
if (evt.IsForOpen)
{
// Insert the current node into the scan line.
scanLine.Insert(currentNode);
#if VERBOSE
System.Diagnostics.Debug.WriteLine("ScanAdd: {0}", currentNode);
#endif // VERBOSE
// Find the nodes that are currently open to either side of it and are either overlapping
// nodes or the first non-overlapping node in that direction.
currentNode.LeftNeighbors = GetLeftNeighbours(parameters, scanLine, currentNode, isHorizontal);
currentNode.RightNeighbors = GetRightNeighbours(parameters, scanLine, currentNode, isHorizontal);
// Use counts for indexing for performance (rather than foreach, and hoist the count-control
// variable out of the loop so .Count isn't checked on each iteration, since we know it's
// not going to be changed).
int numLeftNeighbors = currentNode.LeftNeighbors.Count;
int numRightNeighbors = currentNode.RightNeighbors.Count;
// If there is currently a non-overlap constraint between any two nodes across the
// two neighbour lists we've just created, we can remove them because they will be
// transitively enforced by the constraints we'll create for the current node.
// I.e., we can remove the specification for the constraint
// leftNeighborNode + gap + padding <= rightNeighborNode
// because it is implied by the constraints we'll create for
// leftNeighborNode + gap + padding <= node
// node + gap + padding <= rightNeighborNode
// We must also add the current node as a neighbour in the appropriate direction.
// @@PERF: List<T>.Remove is a sequential search so averages 1/2 the size of the
// lists. We currently don't expect the neighbour lists to be very large or Remove
// to be a frequent operation, and using HashSets would incur the GetEnumerator overhead
// on the outer and inner loops; but .Remove creates an inner-inner loop so do some
// timing runs to compare performance.
// @@PERF: handles the case where we are node c and have added node b as a lnbour
// and node d as rnbour, where those nodes are already nbours. But it does not handle
// the case where we add node b and node a as lnbours, and node b already has node a
// as an lnbour. To do this I think we'd just want to skip adding the node-a lnbour,
// but that forms a new inner loop (iterating all lnbours before adding a new one)
// unless we develop different storage for nbours.
for (int ii = 0; ii < numLeftNeighbors; ++ii)
{
OverlapRemovalNode leftNeighborNode = currentNode.LeftNeighbors[ii];
for (int jj = 0; jj < numRightNeighbors; ++jj)
{ // TODOunit: test this
OverlapRemovalNode nodeToRemove = currentNode.RightNeighbors[jj];
if (leftNeighborNode.RightNeighbors.Remove(nodeToRemove))
{
#if VERBOSE
System.Diagnostics.Debug.WriteLine(" {0} RnbourRem {1} --> {2}", isHorizontal ? "H" : "V", leftNeighborNode, nodeToRemove);
#endif // VERBOSE
}
}
leftNeighborNode.RightNeighbors.Add(currentNode);
}
for (int ii = 0; ii < numRightNeighbors; ++ii)
{ // TODOunit: test this
OverlapRemovalNode rightNeighborNode = currentNode.RightNeighbors[ii];
for (int jj = 0; jj < numLeftNeighbors; ++jj)
{
OverlapRemovalNode nodeToRemove = currentNode.LeftNeighbors[jj];
if (rightNeighborNode.LeftNeighbors.Remove(nodeToRemove))
{
#if VERBOSE
System.Diagnostics.Debug.WriteLine(" {0} LnbourRem {1} --> {2}", isHorizontal ? "H" : "V", nodeToRemove, rightNeighborNode);
#endif // VERBOSE
}
}
rightNeighborNode.LeftNeighbors.Add(currentNode);
}
} // endif evt.IsForOpen
else
{
// This is a close event, so generate the constraints and remove the closing node
// from its neighbours lists. If we're closing we should have left neighbours so
// this is null then we've likely got some sort of internal calculation error.
if (null == currentNode.LeftNeighbors)
{
Debug.Assert(null != currentNode.LeftNeighbors, "LeftNeighbors should not be null for a Close event");
continue;
}
// currentNode is the current node; if it's a cluster, translate it to the node that
// should be involved in the constraint (if it's the left neighbour then use its
// right border as the constraint variable, and vice-versa).
OverlapRemovalNode currentLeftNode = GetLeftConstraintNode(currentNode);
OverlapRemovalNode currentRightNode = GetRightConstraintNode(currentNode);
// LeftNeighbors must end before the current node...
int cLeftNeighbours = currentNode.LeftNeighbors.Count;
for (int ii = 0; ii < cLeftNeighbours; ++ii)
{
// Keep track of the original Node; it may be the base of a Cluster, in which
// case it will have the active neighbours list, not leftNeighborNode (which will
// be the left border "fake Node").
OverlapRemovalNode origLeftNeighborNode = currentNode.LeftNeighbors[ii];
origLeftNeighborNode.RightNeighbors.Remove(currentNode);
OverlapRemovalNode leftNeighborNode = GetLeftConstraintNode(origLeftNeighborNode);
Debug.Assert(leftNeighborNode.OpenP == origLeftNeighborNode.OpenP, "leftNeighborNode.OpenP must == origLeftNeighborNode.OpenP");
// This assert verifies we match the Solver.ViolationTolerance check in AddNeighbor.
// We are closing the node here so use an alternative to OverlapP for additional
// consistency verification. Allow a little rounding error.
Debug.Assert(isHorizontal
|| ((currentNode.CloseP + NodePaddingP - leftNeighborNode.OpenP) > (parameters.SolverParameters.GapTolerance - 1e-6)),
"LeftNeighbors: unexpected close/open overlap");
double p = leftNeighborNode == LeftBorderNode || currentRightNode == RightBorderNode ? ClusterPadding : NodePadding;
double separation = ((leftNeighborNode.Size + currentRightNode.Size) / 2) + p;
if (TranslateChildren)
{
separation = Math.Max(separation, currentRightNode.Position - leftNeighborNode.Position);
}
Constraint cst = solver.AddConstraint(leftNeighborNode.Variable, currentRightNode.Variable, separation);
Debug.Assert(null != cst, "LeftNeighbors: unexpected null cst");
#if VERBOSE
System.Diagnostics.Debug.WriteLine(" {0} LnbourCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V"
, cst.Left.Name, cst.Right.Name, cst.Gap);
#endif // VERBOSE
}
// ... and RightNeighbors must start after the current node.
int cRightNeighbours = currentNode.RightNeighbors.Count;
for (int ii = 0; ii < cRightNeighbours; ++ii)
{
// Keep original node, which may be a cluster; see comments in LeftNeighbors above.
OverlapRemovalNode origRightNeighborNode = currentNode.RightNeighbors[ii];
origRightNeighborNode.LeftNeighbors.Remove(currentNode);
OverlapRemovalNode rightNeighborNode = GetRightConstraintNode(origRightNeighborNode);
// This assert verifies we match the Solver.ViolationTolerance check in AddNeighbor.
// Allow a little rounding error.
Debug.Assert(isHorizontal
|| ((currentNode.CloseP + NodePaddingP - rightNeighborNode.OpenP) > (parameters.SolverParameters.GapTolerance - 1e-6)),
"RightNeighbors: unexpected close/open overlap");
double p = currentLeftNode == LeftBorderNode || rightNeighborNode == RightBorderNode ? ClusterPadding : NodePadding;
double separation = ((currentLeftNode.Size + rightNeighborNode.Size) / 2) + p;
if (TranslateChildren)
{
separation = Math.Max(separation, rightNeighborNode.Position - currentLeftNode.Position);
}
Constraint cst = solver.AddConstraint(currentLeftNode.Variable, rightNeighborNode.Variable, separation);
Debug.Assert(null != cst, "RightNeighbors: unexpected null cst");
#if VERBOSE
System.Diagnostics.Debug.WriteLine(" {0} RnbourCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V"
, cst.Left.Name, cst.Right.Name, cst.Gap);
#endif // VERBOSE
}
// Note: although currentNode is closed, there may still be open nodes in its
// Neighbour lists; these will subsequently be processed (and removed from
// currentNode.*Neighbour) when those Neighbors are closed.
scanLine.Remove(currentNode);
#if VERBOSE
System.Diagnostics.Debug.WriteLine("ScanRem: {0}", currentNode);
#endif // VERBOSE
} // endelse !evt.IsForOpen
// @@PERF: Set Node.Left/RightNeighbors null to let the GC know we're not using them
// anymore, unless we can reasonably assume a short lifetime for the ConstraintGenerator.
} // endforeach Event
}