(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))