git.fiddlerwoaroof.com
quadtree.lisp
4197362e
 (defpackage quadtree
   (:use :cl)
   (:export make-rect quick-point new-quadtree contains-point intersects-boundary
            apply-to-children query-range insert))
 
 (in-package :quadtree)
 
 (defstruct point x y)
 (defstruct size  w h)
 (defstruct bounds center size)
 (defun make-rect (x y w h)
   (make-bounds
     :center (make-point :x x :y y)
     :size   (make-size :w w :h h)))
 
 (defclass quadtree ()
 
   ((bounds :initarg :bounds
            :initform (error "must provide boundaries"))
    (point-count :initform 1)
    (points :initform (make-array 5 :fill-pointer 0 :adjustable t))
    (nw :initform nil :type quadtree)
    (ne :initform nil :type quadtree)
    (sw :initform nil :type quadtree)
    (se :initform nil :type quadtree)))
 
 (defun twiddle (lis prefix &rest end)
   (cond ((null lis) `(progn ,@end))
         (t (let ((a (car lis))
                  (b (cdr lis)))
              `(,prefix ,(car a) ,(cadr a) ,(apply #'twiddle b prefix end))))))
 
 (defmacro mwith-slots (arg-forms &body rst)
   (apply #'twiddle arg-forms 'with-slots rst))
 
 (defmacro with-foursides (names center size &body body)
   "Binds the coordinates of the foor sides of a region: the names are bound to w,e,s,n in that order"
   `(mwith-slots (((x y) ,center)
                  ((w h) ,size))
      (let ((,(first names) (- x w))
            (,(second names) (+ x w))
            (,(third names) (- y h))
            (,(fourth names) (+ y h)))
        ,@body)))
 
 (defmacro with-bounds-foursides (names bounds &body bod)
   `(with-slots ((a center) (b size)) ,bounds
      (with-foursides ,names a b ,@bod)))
 
 (defun contains-point (bounds point)
   (with-slots ((x2 x) (y2 y)) point
     (with-bounds-foursides (w_x e_x s_y n_y) bounds
       (and (and (<= w_x x2) (> e_x x2))
            (and (<= s_y y2) (> n_y y2))))))
 
 (defun between (x a b)
   "Return T if x is between a and b (order insensitive)"
   (if (> a b) (rotatef a b)) (and (< a x) (< x b)))
 
 (defun quick-point (x y) (make-point :x x :y y))
 
 (defun intersects~boundary (bounds1 bounds2)
   (flet
     ((inner (bounds1 bounds2)
        (with-bounds-foursides (w_x1 e_x1 s_y1 n_y1) bounds1
          (with-bounds-foursides (w_x2 e_x2 s_y2 n_y2) bounds2
            (or (contains-point bounds1 (quick-point w_x2 n_y2))
                (contains-point bounds1 (quick-point e_x2 n_y2))
                (contains-point bounds1 (quick-point w_x2 s_y2))
                (contains-point bounds1 (quick-point e_x2 s_y2)))
            ))))
     (or (inner bounds1 bounds2) (inner bounds2 bounds1))))
 
 (defmacro combine (names func first_forms second_forms &body body)
   (flet ((name_combs (n1s n2s) (loop for n1 in n1s append (loop for n2 in n2s collect (list n1 n2)))))
     `(let
        ,(loop for n in names and cs in (name_combs first_forms second_forms) collect (list n (cons func cs)))
        (progn ,@body))))
 
 (defun subdivide (quadtree)
   (flet ((divquad (bounds)
            (mwith-slots (((center size) bounds)
                          ((x y) center)
                          ((w h) size))
              (let* ((half_w (/ w 2))
                     (w_x (- x half_w))
                     (e_x (+ x half_w))
                     (half_h (/ h 2))
                     (n_y (- y half_h))
                     (s_y (+ y half_h))
                     (newsize (make-size :w half_w :h half_h)))
                (combine (p1 p2 p3 p4) quick-point (n_y s_y) (w_x e_x)
                  (vector (make-bounds :center p1 :size newsize)
                        (make-bounds :center p2 :size newsize)
                        (make-bounds :center p3 :size newsize)
                        (make-bounds :center p4 :size newsize)))))))
     (let ((bounds (divquad (slot-value quadtree 'bounds))))
       (with-slots (nw sw ne se) quadtree
         (if (null nw) (setf nw (make-instance 'quadtree :bounds (elt bounds 0))))
         (if (null ne) (setf ne (make-instance 'quadtree :bounds (elt bounds 1)))) 
         (if (null sw) (setf sw (make-instance 'quadtree :bounds (elt bounds 2)))) 
         (if (null se) (setf se (make-instance 'quadtree :bounds (elt bounds 3))))))))
 
 (defmacro apply-to-children (quadtree func &rest args)
   (let*
     ((nw (gensym))
      (ne (gensym))
      (sw (gensym))
      (se (gensym))
      (qt (gensym))
      )
     `(let ((,qt quadtree))
        (with-slots (,nw ,ne ,sw ,se) ,qt
          ,@(loop for x in `(,nw ,ne ,sw ,se) collect
                  `(,func ,x ,@args))))))
 
 (defun insert (quadtree point)
   (with-slots (bounds points point-count nw ne sw se) quadtree
     (if (contains-point bounds point)
       (if (< (length points) point-count)
         (progn
           (vector-push-extend point points)
           t)
         (progn
           (subdivide quadtree)
           (or (apply-to-children quadtree insert point))))
       nil))) 
 
 (defun query-range (quadtree bounds)
   (let ((pir (make-array 5 :fill-pointer 0 :adjustable t)))
     (with-slots (qbounds qpoints nw) quadtree
       (if (intersects-boundary qbounds bounds)
         (progn
           (loop for p across qpoints
                 if (contains-point bounds p)
                 do (vector-push-extend p pir))
           (if (not (null nw))
             (setf pir
                   (concatenate 'vector
                                (apply-to-children quadtree query-range bounds)))))))
     pir))