public bool intersects(BoundingBox bbox)
 {
     return !(this.layout_x + this.layout_width < bbox.layout_x       // left
             || this.layout_x > bbox.layout_x + bbox.layout_width     // right
             || this.layout_y + this.layout_height < bbox.layout_y    // up
             || this.layout_y > bbox.layout_y + bbox.layout_height);  // down
 }
 public bool contains(BoundingBox bbox)
 {
     return (bbox.layout_x > this.layout_x
             && bbox.layout_x + bbox.layout_width < this.layout_x + this.layout_width
             && bbox.layout_y > this.layout_y
             && bbox.layout_y + bbox.layout_height < this.layout_y + this.layout_height);
 }
        // sample test request http://localhost:4949/api/Timeline?x=0.5&y=0.5&w=0.2&h=0.2&s=0.001
        // scale t/px
        public Timeline Get(double x, double y, double w, double h, double s)
        {
            var root = Globals.SceneGraph;
            var wnd = new BoundingBox(x, y, w, h);

            if (root == null)
            {
                return null;
            }
            else if (root.contains(wnd))
            {
                Timeline lca = null;
                // find path(data, wnd)
                // find lca (least common ancestor)
                var path = getPath(root, wnd, ref lca);
                // verify lca is visible
                if (lca.isVisible(s))
                {
                    // find content(lca, wnd)
                    var content = getContent(lca, wnd, s);
                    // find plca (parent of lca)
                    Timeline plca;
                    for (plca = null, lca = path; lca.ChildTimelines.Count == 1; plca = lca, lca = lca.ChildTimelines[0]) ;
                    // merge path and content
                    if (plca != null)
                    {
                        plca.ChildTimelines.Clear();
                        plca.ChildTimelines.Add(content);
                    }
                    else
                    {
                        path = content;
                    }
                }
                else
                {
                    // path = null;
                }

                return path;
            }
            else if (root.intersects(wnd) && root.isVisible(s))
            {
                // find content
                var content = getContent(root, wnd, s);
                return content;
            }
            else
            {
                return null;
            }
        }
        public Timeline getContent(Timeline tl, BoundingBox wnd, double scale)
        {
            // assert: tl intersects wnd and is visible
            // => tl is part of content
            // => tl is loaded
            // => return tl with ChildTimelines

            var content = tl.clone();
            content.isBuffered = true;

            foreach (var child in tl.ChildTimelines)
            {
                if (child.intersects(wnd) && child.isVisible(scale))
                    content.ChildTimelines.Add(getContent(child, wnd, scale));
                else
                    content.ChildTimelines.Add(child.clone());
            }

            return content;
        }
        public Timeline getPath(Timeline tl, BoundingBox wnd, ref Timeline lca)
        {
            // assert: tl contains wnd
            // => tl is part of path

            var path = tl.clone();
            path.Exhibits = new List<Exhibit>();
            lca = tl;

            foreach (var child in tl.ChildTimelines)
            {
                if (child.contains(wnd))
                {
                    path.ChildTimelines.Add(getPath(child, wnd, ref lca));
                    break;
                }
            }

            return path;
        }