| 1 | (in-package :geometry) |
|---|
| 2 | |
|---|
| 3 | ;; a point in this package is represented |
|---|
| 4 | ;; as a list (x y) |
|---|
| 5 | |
|---|
| 6 | ;; a rectangle is represented |
|---|
| 7 | ;; as a list (left top width height) |
|---|
| 8 | |
|---|
| 9 | (defmacro with-point (point &body body) |
|---|
| 10 | (let* ((*package* (symbol-package point)) |
|---|
| 11 | (x (intern (format nil "~A-X" (symbol-name point)))) |
|---|
| 12 | (y (intern (format nil "~A-Y" (symbol-name point))))) |
|---|
| 13 | `(destructuring-bind (,x ,y) ,point |
|---|
| 14 | ,@body))) |
|---|
| 15 | |
|---|
| 16 | (defmacro with-points ((&rest points) &body body) |
|---|
| 17 | (if (null points) |
|---|
| 18 | `(progn ,@body) |
|---|
| 19 | `(with-point ,(car points) |
|---|
| 20 | (with-points (,@(cdr points)) |
|---|
| 21 | ,@body)))) |
|---|
| 22 | |
|---|
| 23 | (defmacro with-rectangle (rectangle-or-options &body body) |
|---|
| 24 | (destructuring-bind (rectangle &key suffix) (ensure-list rectangle-or-options) |
|---|
| 25 | (flet ((add-suffix (symbol) |
|---|
| 26 | (if suffix |
|---|
| 27 | (intern (format nil "~a-~a" (symbol-name symbol) (string-upcase suffix))) |
|---|
| 28 | (intern (symbol-name symbol))))) |
|---|
| 29 | `(destructuring-bind (,(add-suffix 'left) |
|---|
| 30 | ,(add-suffix 'top) |
|---|
| 31 | ,(add-suffix 'width) |
|---|
| 32 | ,(add-suffix 'height)) |
|---|
| 33 | ,rectangle |
|---|
| 34 | ,@body)))) |
|---|
| 35 | |
|---|
| 36 | (defun distance (point-a point-b) |
|---|
| 37 | (with-points (point-a point-b) |
|---|
| 38 | (sqrt (+ (expt (- point-a-x point-b-x) 2) |
|---|
| 39 | (expt (- point-a-y point-b-y) 2))))) |
|---|
| 40 | |
|---|
| 41 | (defmacro dorect ((point (left top width height) &key test row-change) &body body) |
|---|
| 42 | "Iterate with POINT over all points in rect row per row. The list |
|---|
| 43 | containing x and y is intended for only extracting those |
|---|
| 44 | and not to be stored away (it will be modified). |
|---|
| 45 | |
|---|
| 46 | BODY is only executed, if TEST of the current point is true. |
|---|
| 47 | |
|---|
| 48 | For convenience, a null arg function ROW-CHANGE can be given |
|---|
| 49 | that will be called between the rows." |
|---|
| 50 | (check-type point symbol) |
|---|
| 51 | (rebinding (left top) |
|---|
| 52 | `(iter |
|---|
| 53 | (with ,point = (list nil nil)) |
|---|
| 54 | (for y from ,top to (1- (+ ,top ,height))) |
|---|
| 55 | ,(when row-change |
|---|
| 56 | `(unless (first-time-p) |
|---|
| 57 | (funcall ,row-change))) |
|---|
| 58 | (iter |
|---|
| 59 | (for x from ,left to (1- (+ ,left ,width))) |
|---|
| 60 | (setf (first ,point) x |
|---|
| 61 | (second ,point) y) |
|---|
| 62 | (when ,(if test |
|---|
| 63 | `(funcall ,test ,point) |
|---|
| 64 | t) |
|---|
| 65 | ,@body))))) |
|---|
| 66 | |
|---|
| 67 | (defun rectangle-center (rectangle &key roundp) |
|---|
| 68 | (with-rectangle rectangle |
|---|
| 69 | (let ((x (+ left (/ width 2))) |
|---|
| 70 | (y (+ top (/ height 2)))) |
|---|
| 71 | (if roundp |
|---|
| 72 | (list (round x) (round y)) |
|---|
| 73 | (list x y))))) |
|---|
| 74 | |
|---|
| 75 | (defun rectangle-intersects-p (a b) |
|---|
| 76 | (with-rectangle (a :suffix a) |
|---|
| 77 | (with-rectangle (b :suffix b) |
|---|
| 78 | (let* ((right-a (+ left-a width-a)) |
|---|
| 79 | (bottom-a (+ top-a height-a)) |
|---|
| 80 | (right-b (+ left-b width-b)) |
|---|
| 81 | (bottom-b (+ top-b height-b)) |
|---|
| 82 | (left (max left-a left-b)) |
|---|
| 83 | (top (max top-a top-b)) |
|---|
| 84 | (right (min right-a right-b)) |
|---|
| 85 | (bottom (min bottom-a bottom-b))) |
|---|
| 86 | (and (> right left) (> bottom top)))))) |
|---|
| 87 | |
|---|
| 88 | ;; maybe change this function to take a |
|---|
| 89 | ;; point as an argument? |
|---|
| 90 | (defun point-in-polygon-p (x y polygon) |
|---|
| 91 | (let (result |
|---|
| 92 | (py y)) |
|---|
| 93 | (loop with (pjx . pjy) = (aref polygon (1- (length polygon))) |
|---|
| 94 | for (pix . piy) across polygon |
|---|
| 95 | when (and (or (and (<= piy py) (< py pjy)) |
|---|
| 96 | (and (<= pjy py) (< py piy))) |
|---|
| 97 | (< x |
|---|
| 98 | (+ (/ (* (- pjx pix) (- py piy)) |
|---|
| 99 | (- pjy piy)) |
|---|
| 100 | pix))) |
|---|
| 101 | do (setf result (not result)) |
|---|
| 102 | do (setf pjx pix |
|---|
| 103 | pjy piy)) |
|---|
| 104 | result)) |
|---|
| 105 | |
|---|
| 106 | (defun point-in-circle-p (point center radius) |
|---|
| 107 | (<= (distance point center) radius)) |
|---|
| 108 | |
|---|
| 109 | (defun point-in-rect-p (point rectangle) |
|---|
| 110 | (with-point point |
|---|
| 111 | (with-rectangle rectangle |
|---|
| 112 | (and (<= left point-x) |
|---|
| 113 | (< point-x (+ left width)) |
|---|
| 114 | (<= top point-y) |
|---|
| 115 | (< point-y (+ top height)))))) |
|---|
| 116 | |
|---|
| 117 | ;;; for fun... |
|---|
| 118 | ;; (defun point-in-circle-p-test () |
|---|
| 119 | ;; (let ((center (list 4 4))) |
|---|
| 120 | ;; (dorect (p (0 0 10 10) :row-change #'terpri) |
|---|
| 121 | ;; (if (point-in-circle-p p center 3) |
|---|
| 122 | ;; (princ "x") |
|---|
| 123 | ;; (princ "."))))) |
|---|
| 124 | |
|---|
| 125 | (defun bounding-box (objects &key (key #'identity)) |
|---|
| 126 | (let (min-x min-y max-x max-y) |
|---|
| 127 | (dolist (obj objects) |
|---|
| 128 | (let ((point (funcall key obj))) |
|---|
| 129 | (with-point point |
|---|
| 130 | (setf min-x (min point-x (or min-x point-x))) |
|---|
| 131 | (setf min-y (min point-y (or min-y point-y))) |
|---|
| 132 | (setf max-x (max point-x (or max-x point-x))) |
|---|
| 133 | (setf max-y (max point-y (or max-y point-y)))))) |
|---|
| 134 | (list min-x min-y (1+ (- max-x min-x)) (1+ (- max-y min-y))))) |
|---|
| 135 | |
|---|
| 136 | (defmacro with-bounding-box-collect ((collect) &body body) |
|---|
| 137 | `(let (min-x min-y max-x max-y) |
|---|
| 138 | (flet ((,collect (point) |
|---|
| 139 | (with-point point |
|---|
| 140 | (setf min-x (min point-x (or min-x point-x))) |
|---|
| 141 | (setf min-y (min point-y (or min-y point-y))) |
|---|
| 142 | (setf max-x (max point-x (or max-x point-x))) |
|---|
| 143 | (setf max-y (max point-y (or max-y point-y)))))) |
|---|
| 144 | ,@body) |
|---|
| 145 | (when min-x |
|---|
| 146 | (list min-x min-y (1+ (- max-x min-x)) (1+ (- max-y min-y)))))) |
|---|
| 147 | |
|---|
| 148 | ;;; directions |
|---|
| 149 | |
|---|
| 150 | ;; A direction can be represented either |
|---|
| 151 | ;; as one of the symbols: |
|---|
| 152 | ;; :down, :left, :right, :up |
|---|
| 153 | ;; |
|---|
| 154 | ;; or as a list of dx and dy |
|---|
| 155 | ;; which can be used to move from one |
|---|
| 156 | ;; point to another in that direction |
|---|
| 157 | ;; |
|---|
| 158 | ;; the mapping is as follows: |
|---|
| 159 | ;; |
|---|
| 160 | ;; dx dy symbol |
|---|
| 161 | ;; -- -- ----- |
|---|
| 162 | ;; 0 1 :down |
|---|
| 163 | ;; -1 0 :left |
|---|
| 164 | ;; 1 0 :right |
|---|
| 165 | ;; 0 -1 :up |
|---|
| 166 | ;; |
|---|
| 167 | |
|---|
| 168 | (defmethod turn-right ((direction symbol)) |
|---|
| 169 | (case direction |
|---|
| 170 | (:down :left) |
|---|
| 171 | (:left :up) |
|---|
| 172 | (:up :right) |
|---|
| 173 | (:right :down))) |
|---|
| 174 | |
|---|
| 175 | (defmethod turn-right ((direction list)) |
|---|
| 176 | (direction-as-list (turn-right (direction-as-symbol direction)))) |
|---|
| 177 | |
|---|
| 178 | (defmethod turn-left ((direction symbol)) |
|---|
| 179 | (case direction |
|---|
| 180 | (:down :right) |
|---|
| 181 | (:right :up) |
|---|
| 182 | (:up :left) |
|---|
| 183 | (:left :down))) |
|---|
| 184 | |
|---|
| 185 | (defmethod turn-left ((direction list)) |
|---|
| 186 | (direction-as-list (turn-left (direction-as-symbol direction)))) |
|---|
| 187 | |
|---|
| 188 | (defmethod direction-as-symbol ((direction symbol)) |
|---|
| 189 | direction) |
|---|
| 190 | |
|---|
| 191 | (defmethod direction-as-symbol ((direction list)) |
|---|
| 192 | (arnesi:switch (direction :test #'equal) |
|---|
| 193 | (((0 1)) :down) |
|---|
| 194 | (((-1 0)) :left) |
|---|
| 195 | (((1 0)) :right) |
|---|
| 196 | (((0 -1)) :up))) |
|---|
| 197 | |
|---|
| 198 | (defmethod direction-as-list ((direction list)) |
|---|
| 199 | direction) |
|---|
| 200 | |
|---|
| 201 | (defmethod direction-as-list ((direction symbol)) |
|---|
| 202 | (case direction |
|---|
| 203 | (:down '(0 1)) |
|---|
| 204 | (:left '(-1 0)) |
|---|
| 205 | (:right '(1 0)) |
|---|
| 206 | (:up '(0 -1)))) |
|---|
| 207 | |
|---|
| 208 | (defmethod move ((point list) direction) |
|---|
| 209 | (destructuring-bind (x y) |
|---|
| 210 | point |
|---|
| 211 | (destructuring-bind (dx dy) |
|---|
| 212 | (direction-as-list direction) |
|---|
| 213 | (list (+ x dx) |
|---|
| 214 | (+ y dy))))) |
|---|
| 215 | |
|---|
| 216 | ;;; TODO add eql for directions ? |
|---|
| 217 | |
|---|
| 218 | (defun find-boundary-point (point in-region-p &optional (direction :up)) |
|---|
| 219 | (let* ((direction (direction-as-list direction)) |
|---|
| 220 | (next (move point direction))) |
|---|
| 221 | (if (funcall in-region-p next) |
|---|
| 222 | (find-boundary-point next in-region-p) |
|---|
| 223 | point))) |
|---|
| 224 | |
|---|
| 225 | ;;; region-to-polygon |
|---|
| 226 | (defun region-to-polygon (point in-region-p) |
|---|
| 227 | "Will return a closed path of points in mathematical order. |
|---|
| 228 | IN-REGION-P is a predicate that takes a point as an argument. |
|---|
| 229 | It defines the region whose bounding polygon is to be found." |
|---|
| 230 | (let ((polygon) |
|---|
| 231 | (count 0) |
|---|
| 232 | (boundary-point (find-boundary-point point in-region-p :up)) |
|---|
| 233 | (initial-direction :left)) |
|---|
| 234 | (labels ((neighbour (point direction) |
|---|
| 235 | "Validate the NEIGHBOUR of POINT in DIRECTION, |
|---|
| 236 | if it is part of the region, returns (NEIGHBOUR DIRECTION), |
|---|
| 237 | otherwise returns NIL." |
|---|
| 238 | (when point |
|---|
| 239 | (let ((neighbour (move point direction))) |
|---|
| 240 | (when (funcall in-region-p neighbour) |
|---|
| 241 | (list neighbour direction))))) |
|---|
| 242 | (choose-next (point direction) |
|---|
| 243 | "Returns a place to move to next as a list (NEXT-POINT NEXT-DIRECTION). |
|---|
| 244 | NEXT-POINT can be the same POINT (but then with a different direction." |
|---|
| 245 | (acond |
|---|
| 246 | ((neighbour point (turn-right direction)) it) |
|---|
| 247 | ((neighbour (first (neighbour point direction)) |
|---|
| 248 | (turn-right direction)) |
|---|
| 249 | it) |
|---|
| 250 | ((neighbour point direction) it) |
|---|
| 251 | (t (list point (turn-left direction))))) |
|---|
| 252 | (terminate (point direction) |
|---|
| 253 | "Are we done?" |
|---|
| 254 | (when (and (eql direction initial-direction) |
|---|
| 255 | (equal point boundary-point)) |
|---|
| 256 | (incf count) |
|---|
| 257 | (= 2 count))) |
|---|
| 258 | (push-point (point direction) |
|---|
| 259 | "Add a point to POLYGON. The actual point |
|---|
| 260 | depends on the DIRECTION." |
|---|
| 261 | (push |
|---|
| 262 | (case direction |
|---|
| 263 | (:left point) |
|---|
| 264 | (:down (move point :down)) |
|---|
| 265 | (:right (move (move point :down) :right)) |
|---|
| 266 | (:up (move point :right))) |
|---|
| 267 | polygon)) |
|---|
| 268 | (traverse (point direction) |
|---|
| 269 | "Go to next POINT by DIRECTION." |
|---|
| 270 | (push-point point direction) |
|---|
| 271 | (unless (terminate point direction) |
|---|
| 272 | (destructuring-bind (next-point next-direction) |
|---|
| 273 | (choose-next point direction) |
|---|
| 274 | (traverse next-point next-direction))))) |
|---|
| 275 | (traverse boundary-point initial-direction) |
|---|
| 276 | (nreverse polygon)))) |
|---|
| 277 | |
|---|
| 278 | |
|---|
| 279 | ;;; formatting |
|---|
| 280 | ;; proposed by Michael Weber on alexandria-devel |
|---|
| 281 | (defun format-mixed-radix-number (stream number radix-list format-list |
|---|
| 282 | &key lsb-first leading-zeros |
|---|
| 283 | (trailing-zeros t)) |
|---|
| 284 | "Prints NUMBER to STREAM in mixed-radix RADIX. |
|---|
| 285 | representation-LIST is a list of radixes, least-significant first. |
|---|
| 286 | FORMAT-LIST is a list of format directives, one for each digit. |
|---|
| 287 | When LSB-FIRST is nil (default), print most-significant digit first, |
|---|
| 288 | otherwise least-significant digit first. |
|---|
| 289 | When LEADING-ZEROS and TRAILING-ZEROS are nil, leading and |
|---|
| 290 | trailing zero digits are not printed, respectively. \(default: remove |
|---|
| 291 | leading zeros, keep trailing zeros)" |
|---|
| 292 | (let ((format-pairs |
|---|
| 293 | (loop with digit and fraction |
|---|
| 294 | initially (setf (values number fraction) |
|---|
| 295 | (truncate number)) |
|---|
| 296 | for f-list on format-list |
|---|
| 297 | and r-list = radix-list then (rest r-list) |
|---|
| 298 | collect (list (first f-list) |
|---|
| 299 | (cond ((endp r-list) |
|---|
| 300 | (shiftf number 0)) |
|---|
| 301 | ((rest f-list) |
|---|
| 302 | (setf (values number digit) |
|---|
| 303 | (truncate number (first r-list))) |
|---|
| 304 | digit) |
|---|
| 305 | (t number))) |
|---|
| 306 | into list |
|---|
| 307 | finally (progn |
|---|
| 308 | (incf (cadar list) fraction) |
|---|
| 309 | (return (nreverse list)))))) |
|---|
| 310 | (unless trailing-zeros |
|---|
| 311 | (setf format-pairs (member-if #'plusp format-pairs :key |
|---|
| 312 | #'second))) |
|---|
| 313 | (when lsb-first |
|---|
| 314 | (setf format-pairs (nreverse format-pairs))) |
|---|
| 315 | (unless leading-zeros |
|---|
| 316 | (setf format-pairs (member-if #'plusp format-pairs :key |
|---|
| 317 | #'second))) |
|---|
| 318 | (format stream "~{~{~@?~}~}" format-pairs))) |
|---|
| 319 | |
|---|
| 320 | |
|---|
| 321 | (defun format-decimal-degree (degree) |
|---|
| 322 | (format-mixed-radix-number nil (* 60 60 degree) '(60 60 360) '("~,2FŽŽ" "~DŽ" "~D°"))) |
|---|
| 323 | |
|---|
| 324 | (defun format-lon-lat (stream lon lat) |
|---|
| 325 | (format stream "~A~:[S~;N~], ~A~:[W~;E~]" |
|---|
| 326 | (format-decimal-degree (abs lat)) |
|---|
| 327 | (plusp lat) |
|---|
| 328 | (format-decimal-degree (abs lon)) |
|---|
| 329 | (plusp lon))) |
|---|
| 330 | |
|---|
| 331 | ;;; publish - subscribe on rectangles |
|---|
| 332 | (defstruct rect-publisher |
|---|
| 333 | subscribers) |
|---|
| 334 | |
|---|
| 335 | (setf (documentation 'make-rect-publisher 'function) |
|---|
| 336 | "MAKE-RECT-PUBLISHER creates a new publisher object.") |
|---|
| 337 | |
|---|
| 338 | (defstruct rect-subscriber |
|---|
| 339 | object rectangle callback-fn) |
|---|
| 340 | |
|---|
| 341 | (defun register-rect-subscriber (publisher subscriber rectangle callback-fn) |
|---|
| 342 | "Register SUBSCRIBER with associated RECTANGLE and CALLBACK-FN with |
|---|
| 343 | PUBLISHER, so that on changes in RECTANGLE, CALLBACK-FN will be called |
|---|
| 344 | with SUBSCRIBER and the published INFO as additional args." |
|---|
| 345 | (push (make-rect-subscriber :object subscriber :rectangle (copy-list rectangle) :callback-fn callback-fn) |
|---|
| 346 | (rect-publisher-subscribers publisher))) |
|---|
| 347 | |
|---|
| 348 | (defun publish-rect-change (publisher rectangle &rest info) |
|---|
| 349 | "Tells PUBLISHER about changes in RECTANGLE. All subscribers whose |
|---|
| 350 | own rectangle intersects with RECTANGLE will be notified. The kind of |
|---|
| 351 | change can be further specified by INFO." |
|---|
| 352 | (dolist (subscriber (rect-publisher-subscribers publisher)) |
|---|
| 353 | (when (rectangle-intersects-p rectangle (rect-subscriber-rectangle subscriber)) |
|---|
| 354 | ;; (print (rect-subscriber-callback-fn subscriber)) |
|---|
| 355 | (apply (rect-subscriber-callback-fn subscriber) (rect-subscriber-object subscriber) info)))) |
|---|
| 356 | |
|---|
| 357 | |
|---|
| 358 | (in-package :screamer-user) |
|---|
| 359 | |
|---|
| 360 | (export 'largest-rectangle) |
|---|
| 361 | (defun largest-rectangle (bounding-rectangle in-region-p) |
|---|
| 362 | "Returns the largest rectangle inside a region (a polygon), which is |
|---|
| 363 | specified here by its BOUNDING-RECTANGLE and the predicate IN-REGION-P |
|---|
| 364 | that will be called with two arguments X and Y to determine if a given |
|---|
| 365 | point belongs to the region or not." |
|---|
| 366 | (destructuring-bind (l tt w h) |
|---|
| 367 | bounding-rectangle |
|---|
| 368 | (let ((left (an-integer-betweenv l (1- (+ l w)) 'left)) |
|---|
| 369 | (top (an-integer-betweenv tt (1- (+ tt h)) 'top)) |
|---|
| 370 | (width (an-integer-betweenv 1 w 'width)) |
|---|
| 371 | (height (an-integer-betweenv 1 h 'height)) |
|---|
| 372 | (right (an-integer-betweenv (1+ l) (+ l w) 'right)) |
|---|
| 373 | (bottom (an-integer-betweenv (1+ tt) (+ tt h) 'bottom)) |
|---|
| 374 | (area (an-integer-betweenv 1 (* w h) 'area))) |
|---|
| 375 | (assert! (=v width (-v right left))) |
|---|
| 376 | (assert! (=v height (-v bottom top))) |
|---|
| 377 | (assert! (=v area (*v width height))) |
|---|
| 378 | (assert! (funcallv #'(lambda (left top right bottom) |
|---|
| 379 | (block result |
|---|
| 380 | (loop for x from left below right |
|---|
| 381 | do (loop for y from top below bottom |
|---|
| 382 | do (unless (funcall in-region-p x y) (return-from result nil)))) |
|---|
| 383 | (return-from result t))) |
|---|
| 384 | left top right bottom)) |
|---|
| 385 | ;; (best-value (solution (list left top width height) (reorder #'range-size (constantly nil) #'< #'linear-force)) area) |
|---|
| 386 | (first (best-value (solution (list left top width height) (static-ordering #'linear-force)) area))))) |
|---|
| 387 | |
|---|
| 388 | (defun integer-random-force (variable) |
|---|
| 389 | (let ((variable (value-of variable))) |
|---|
| 390 | (when (screamer::variable? variable) |
|---|
| 391 | (screamer::restrict-value! |
|---|
| 392 | variable |
|---|
| 393 | (cond ((not (eq (screamer::variable-enumerated-domain variable) t)) |
|---|
| 394 | (a-member-of (alexandria:shuffle (screamer::variable-enumerated-domain variable)))) |
|---|
| 395 | (t (error "INTEGER-RANDOM-FORCE is currently only implemented for ~ |
|---|
| 396 | variables that have an enumerated domain.")))))) |
|---|
| 397 | (value-of variable)) |
|---|
| 398 | |
|---|
| 399 | (export 'colorize) |
|---|
| 400 | (defun colorize (colors objects neighbours-fn) |
|---|
| 401 | (let* ((number-of-colors (length colors)) |
|---|
| 402 | (object2color-var (make-hash-table)) |
|---|
| 403 | (color-vars (mapcar #'(lambda (obj) |
|---|
| 404 | (setf (gethash obj object2color-var) |
|---|
| 405 | (an-integer-betweenv 1 number-of-colors))) |
|---|
| 406 | objects)) |
|---|
| 407 | (hash (make-hash-table :size (hash-table-size object2color-var)))) |
|---|
| 408 | (dolist (obj objects) |
|---|
| 409 | (setf (gethash obj hash) nil)) |
|---|
| 410 | (loop for obj in objects |
|---|
| 411 | for obj-color in color-vars |
|---|
| 412 | do (dolist (neighbour (funcall neighbours-fn obj)) |
|---|
| 413 | (unless (member obj (gethash neighbour hash)) |
|---|
| 414 | (let ((neighbour-color (gethash neighbour object2color-var))) |
|---|
| 415 | (assert! (notv (=v obj-color neighbour-color))) |
|---|
| 416 | (push obj (gethash neighbour hash)))))) |
|---|
| 417 | (one-value (mapcar #'(lambda (color-index) (nth (1- color-index) colors)) |
|---|
| 418 | (solution color-vars (static-ordering #'integer-random-force))) |
|---|
| 419 | (error "no solution to colorize problem")))) |
|---|
| 420 | |
|---|