public IEnumerable <T> Cull(ImageViewState viewState) { var elements = new List <T>(); if (this.QuadTree.RootNode != null) { var cullRect = new Rect(0, 0, viewState.ViewportWidth, viewState.ViewportHeight); var cullContext = new CullContext(cullRect, viewState); CullRecursive(elements, cullContext, this.QuadTree.RootNode); } return(elements); }
private static void CullRecursive( List <T> elements, CullContext context, PointQuadTree <QuadTreeItem> .Node node) { var intersection = CheckIntersection(context, ToRect(node.Bounds)); if (intersection == Intersection.Contain) { #if DEBUG_PRINT_CULLING_PROCESS if (_enablePrintCullingProcess) { Logger.Debug( $"{node.DebugDisplayName} fully contained by view, adding itself and all descendants"); if (node.Element != null) { Logger.Debug($"\tADDED {node.DebugDisplayName} {node.Element.Element}"); } foreach (var descendant in node.EnumerateDescendantNodes()) { if (descendant.Element != null) { Logger.Debug($"\tADDED {descendant.DebugDisplayName} {descendant.Element.Element}"); } } } #endif if (node.Element != null) { elements.Add(node.Element.Element); } elements.AddRange(node.EnumerateDescendants().Where(i => i.Element != null).Select(i => i.Element)); } else if (intersection == Intersection.Intersect) { var viewPosition = context.ViewState.WorldToViewMatrix.Transform(ToWpfPoint(node.Point)); if (context.CullRect.Contains(viewPosition) && node.Element != null) { #if DEBUG_PRINT_CULLING_PROCESS if (_enablePrintCullingProcess) { Logger.Debug( $"{node.DebugDisplayName} intersects with view, and its Point is contained by view, adding"); Logger.Debug($"\tADDED {node.DebugDisplayName} {node.Element.Element}"); } #endif elements.Add(node.Element.Element); } foreach (var subnode in node.Subnodes) { CullRecursive(elements, context, subnode); } } #if DEBUG_PRINT_CULLING_PROCESS else { if (_enablePrintCullingProcess) { Logger.Debug($"{node.DebugDisplayName} does not intersect with view, ruled out"); if (node.Element != null) { Logger.Debug($"\tRULE-OUT {node.DebugDisplayName} {node.Element.Element}"); } foreach (var descendant in node.EnumerateDescendantNodes()) { if (descendant.Element != null) { Logger.Debug($"\tRULE-OUT {descendant.DebugDisplayName} {descendant.Element.Element}"); } } } } #endif }
/// <summary> /// Check whether target rect, in world space, intersects with the cull rect /// </summary> protected static Intersection CheckIntersection(CullContext context, Rect targetRect) { // This method is a simplified implementation with the knowledge of CullRect being unrotated var targetPointsInViewSpace = context.ViewState.WorldToViewMatrix.TransformVertices(targetRect); // first check whether any or all vertices of targetRect, transformed to the view space, // is contained by the cull rect var contained = false; var fullyContained = true; foreach (var point in targetPointsInViewSpace) { if (context.CullRect.Contains(point)) { contained = true; if (!fullyContained) { return(Intersection.Intersect); } } else { fullyContained = false; } } if (fullyContained) { return(Intersection.Contain); } if (contained) { return(Intersection.Intersect); } // then we go with the SAT (separating axis theorem) checking var targetVertices = targetRect.GetVertices(); foreach (var vertices in new[] { context.WorldCullRectVertices, targetVertices }) { for (var i1 = 0; i1 < vertices.Length; i1++) { var i2 = (i1 + 1) % vertices.Length; var p1 = vertices[i1]; var p2 = vertices[i2]; var normalY = p1.X - p2.X; var normalX = p2.Y - p1.Y; var minA = double.MaxValue; var maxA = double.MinValue; foreach (var p in context.WorldCullRectVertices) { var projected = normalX * p.X + normalY * p.Y; if (projected < minA) { minA = projected; } if (projected > maxA) { maxA = projected; } } var minB = double.MaxValue; var maxB = double.MinValue; foreach (var p in targetVertices) { var projected = normalX * p.X + normalY * p.Y; if (projected < minB) { minB = projected; } if (projected > maxB) { maxB = projected; } } if (maxA < minB || maxB < minA) { return(Intersection.NotIntersect); } } } return(Intersection.Intersect); }