anchor.diff:          granularity : float
       }
   
  +    override x.GetHashCode() = x.id.GetHashCode()
  +    override x.Equals o =
  +        match o with
  +            | :? Node<'a> as o -> x.id.Equals(o.id)
  +            | _ -> false
  +
  +
   type IDataProvider<'a when 'a : equality> =
       abstract member BoundingBox : Box3d
       abstract member Traverse : (Node<'a> -> bool) -> unit
       abstract member GetData : node : 'a -> count : int -> Async<IndexedGeometry>
   
  -type DummyDataProvider(root : GridCell) =
  +
  +type DummyDataProvider(root : Box3d) =
       
  -    interface IDataProvider<GridCell> with
  -        member x.BoundingBox = root.Box
  +    interface IDataProvider<Box3d> with
  +        member x.BoundingBox = root
   
           member x.Traverse f =
  -            let rec traverse (b : GridCell) =
  -                let box = b.Box
  +            let rec traverse (level : int) (b : Box3d) =
  +                let box = b
                   let n = 100.0
                   let node = { id = b; bounds = box; inner = true; granularity = Fun.Cbrt(box.Volume / (n*n*n)) }
  +
                   if f node then
  -                    b.Children |> List.iter traverse
  +                    let center = b.Center
  +
  +                    let children =
  +                        let l = b.Min
  +                        let u = b.Max
  +                        let c = center
  +                        [
  +                            Box3d(V3d(l.X, l.Y, l.Z), V3d(c.X, c.Y, c.Z))
  +                            Box3d(V3d(c.X, l.Y, l.Z), V3d(u.X, c.Y, c.Z))
  +                            Box3d(V3d(l.X, c.Y, l.Z), V3d(c.X, u.Y, c.Z))
  +                            Box3d(V3d(c.X, c.Y, l.Z), V3d(u.X, u.Y, c.Z))
  +                            Box3d(V3d(l.X, l.Y, c.Z), V3d(c.X, c.Y, u.Z))
  +                            Box3d(V3d(c.X, l.Y, c.Z), V3d(u.X, c.Y, u.Z))
  +                            Box3d(V3d(l.X, c.Y, c.Z), V3d(c.X, u.Y, u.Z))
  +                            Box3d(V3d(c.X, c.Y, c.Z), V3d(u.X, u.Y, u.Z))
  +                        ]
  +
  +                    children |> List.iter (traverse (level + 1))
                   else
                       ()
  -            traverse root
  -
  -        member x.GetData (cell : GridCell) (count : int) =
  +            traverse 0 root
  +
  +        member x.GetData (cell : Box3d) (count : int) =
               async {
                   return IndexedGeometry()
               }
  
positive.code:     ASet.custom (fun self ->
          let proj = gridProj.GetValue self
          let view = gridCam.GetValue self
          let set = data.Rasterize(CameraView.viewTrafo view, proj, 0.01)
  
          let add = set |> Seq.filter (self.Content.Contains >> not) |> Seq.map Add |> Seq.toArray
          let rem = self.Content |> Seq.filter (set.Contains >> not) |> Seq.map Rem |> Seq.toArray
  
          printfn "+%d / -%d" add.Length rem.Length
  
          Seq.append add rem |> Seq.toList
      )
  
  let boxes = nodes |> ASet.map (fun n -> Helpers.wireBox (Helpers.randomColor()) n.bounds)
  
  let attributeTypes =
      Map.ofList [
  
negative.code:         ASet.AdaptiveSet(fun () -> new CustomReader<'a>(f) :> IReader<_>) :> aset<_>
  
  
      type WithUpdateReader<'a>(m : IMod<unit>, r : IReader<'a>) =
          inherit ASetReaders.AbstractReader<'a>()
  
  
          override x.ComputeDelta() =
              m.GetValue x
              r.GetDelta x
  
          override x.Release() =
              r.Dispose()
  
          override x.Inputs = Seq.ofList [m; r]
  
  
      let withUpdater (m : IMod<unit>) (s : aset<'a>) =
          ASet.AdaptiveSet(fun () -> new WithUpdateReader<_>(m, s.GetReader()) :> IReader<_>) :> aset<_>
  
  
  // ===================================================================================
  // LoD stuff
  // ===================================================================================
  [<CustomEquality; NoComparison>]
  type Node<'a when 'a : equality> =
      {
          id : 'a
          bounds : Box3d
  