root/trunk/projects/bos/m2/geometry.lisp @ 2973

Revision 2973, 14.8 KB (checked in by ksprotte, 2 years ago)

make-contract-tree-from-m2 is now more robust. it erred when there were no allocation-areas / no contracts

Line 
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
43containing x and y is intended for only extracting those
44and not to be stored away (it will be modified).
45
46BODY is only executed, if TEST of the current point is true.
47
48For convenience, a null arg function ROW-CHANGE can be given
49that 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.
228IN-REGION-P is a predicate that takes a point as an argument.
229It 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.
285representation-LIST is a list of radixes, least-significant first.
286FORMAT-LIST is a list of format directives, one for each digit.
287When LSB-FIRST is nil (default), print most-significant digit first,
288otherwise least-significant digit first.
289When LEADING-ZEROS and TRAILING-ZEROS are nil, leading and
290trailing zero digits are not printed, respectively. \(default: remove
291leading 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
343PUBLISHER, so that on changes in RECTANGLE, CALLBACK-FN will be called
344with 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
350own rectangle intersects with RECTANGLE will be notified. The kind of
351change 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
363specified here by its BOUNDING-RECTANGLE and the predicate IN-REGION-P
364that will be called with two arguments X and Y to determine if a given
365point 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
Note: See TracBrowser for help on using the browser.