// sideWithRange is either the same as sideToQueue, if that side is being loaded by an
        // OpenVertexEvent, or is a different side that is just closing.
        protected void LoadReflectionEvents(BasicObstacleSide sideToQueue, BasicObstacleSide sideWithRange) {
            // If this line reflects upward then it cannot receive rays from below (they would pass
            // through its obstacle), and of course a perpendicular lookahead line will never
            // intersect a perpendicular side.
            if ((null == sideToQueue) || SideReflectsUpward(sideToQueue) || IsPerpendicular(sideToQueue)) {
                return;
            }

            // If there is no overlap in the rectangles along the current axis, there is nothing
            // to do.  This reduces (but doesn't prevent) duplicate events being loaded.
            var bbox1 = new Rectangle(sideToQueue.Start, sideToQueue.End);
            var bbox2 = new Rectangle(sideWithRange.Start, sideWithRange.End);
            if ((ScanDirection.IsHorizontal) ? !bbox1.IntersectsOnX(bbox2) : !bbox1.IntersectsOnY(bbox2)) {
                return;
            }

            // Make sure we order the endpoints from low to high along the scanline parallel, and get only
            // the intersection.  RectilinearFileTests.Nudger_Overlap* exercise reflection lookahead subranges.
            Rectangle bboxIntersect = Rectangle.Intersect(bbox1, bbox2);
            Point low = bboxIntersect.LeftBottom;
            Point high = bboxIntersect.RightTop;

            // This is inclusive of the endpoints of sideWithRange, to be sure that we remove the item
            // from LookaheadScan; if it's on an extreme vertex in the perpendicular sweep then it will
            // stop the chain; see TestRectilinear.Reflection_Staircase_Stops_At_BoundingBox_Side*.
            RBNode<BasicReflectionEvent> lookaheadSiteNode = lookaheadScan.FindFirstInRange(low, high);
            while (null != lookaheadSiteNode) {
                // Calculate the lookahead intersection with this side in the perpendicular direction to
                // the scanline.  Note: due to rounding error, this may be different from the calculation
                // in the parallel direction when the scanline gets up to the ScanDirection.PerpCoord(intersect);
                // this will be adjusted in ScanSegmentTree.MergeSegments.
                Point intersect = ScanLineIntersectSide(lookaheadSiteNode.Item.Site, sideToQueue
                            , ScanDirection.PerpendicularInstance);
                DevTraceInfoVgGen(1, "Loading reflection from lookahead site {0} to intersect at {1}"
                            , lookaheadSiteNode.Item.Site, intersect);
                DevTraceInfoVgGen(2, "     side {0})", sideToQueue);    // same indent as AddSegment

                // In some cases where the ActiveLowSide and ActiveHighSide of an obstacle lean in the same
                // direction such that LowSide is above HighSide, e.g. on the horizontal pass when they both
                // lean to the right, the delayed-lookahead in CloseVertex (obstacle close) event may find the
                // high side spanning the scanline-parallel coordinate range where its low side has enqueued
                // lookahead events.  In that case the intersection will be less than the enqueueing site so
                // ignore it.  See RectilinearTests.ReflectionsSitedByLowSideAreNotLoadedByHighSide.)
                // Similarly, if this is at the same perpendicular coordinate as the current scanline
                // position, ignore it; otherwise we could back up in the scanline's parallel coordinate.
                // Since we retrieved events for the endpoint of any previous side, this won't be
                // encountered on a bend vertex event; therefore we're on a near-flat bottom side
                // so we're parallel to the extreme-vertex line and it's fine to just absorb the photon.
                // This also handles the case of reflections into intersecting sides - at some point
                // they converge such that the intersection is not ahead of the lookahead site.
                if (ScanDirection.ComparePerpCoord(intersect, lookaheadSiteNode.Item.Site) > 0) {
                    // Add an event to continue the chain, "shifting" the site's reflecting
                    // obstacle back to the initialObstacle position.  We must load this here 
                    // and process it in ConfirmLookaheadEvent so it will be removed from
                    // the lookahead list; we can't remove it here if it doesn't satisfy the
                    // staircase requirements, because this may be called from loading a "higher"
                    // side (during the sweep) which could steal events from the lower side.
                    AddReflectionEvent(lookaheadSiteNode.Item, sideToQueue, intersect);
                }
                else {
                    if (lookaheadSiteNode.Item.ReflectingObstacle != sideToQueue.Obstacle) {
                        DevTraceInfoVgGen(1, "  (discarding reflection at intersect {0} as it is not ahead of the previous site)", intersect);

                        // We need to remove the site.  We're in the middle of Node enumeration so just
                        // mark the site and on function exit we'll remove any so marked.
                        lookaheadScan.MarkStaleSite(lookaheadSiteNode.Item);
                    }
                    else {
                        DevTraceInfoVgGen(1, "  (skipping reflection at intersect {0} as it is the same obstacle)", intersect);
                    }
                }

                // Get the next item, leaving the current one in the lookahead scan until
                // we actually process the event; this lets us know whether an intervening
                // obstacle may be opened and intercepted the reflection. ConfirmLookaheadEvents
                // will actually do the removal when the lowest side containing the lookahead
                // site is loaded.  See RectilinearTests.ReflectionsRemoveInterceptedSite.
                lookaheadSiteNode = lookaheadScan.FindNextInRange(lookaheadSiteNode, high);
            } // endwhile previousSiteNode

            lookaheadScan.RemoveStaleSites();
        }
        public double GetDistance(Rectangle rect1, Rectangle rect2) {
            if (!Rectangle.Intersect(rect1, rect2).IsEmpty) return 0;

            Rectangle leftmost = rect1.Left <= rect2.Left ? rect1 : rect2;
            Rectangle notLeftmost = rect1.Left <= rect2.Left ? rect2 : rect1;

            Rectangle botommost = rect1.Bottom <= rect2.Bottom ? rect1 : rect2;
            Rectangle notBotommost = rect1.Bottom <= rect2.Bottom ? rect2 : rect1;

            double dx = notLeftmost.Left - leftmost.Right;
            double dy = notBotommost.Bottom - botommost.Top;

            if(rect1.IntersectsOnX(rect2)) return dy;
            if (rect1.IntersectsOnY(rect2)) return dx;

            return Math.Sqrt(dx * dx + dy * dy);
        }