Update of /project/geometry/cvsroot/geometry In directory clnet:/tmp/cvs-serv26628
Added Files: geometry.asd geometry.lisp mcclim-geom-tests.asd mcclim-geom-tests.lisp Log Message: Added various 2D geometry routines and a small McCLIM app to test a proportion of them.
--- /project/geometry/cvsroot/geometry/geometry.asd 2007/10/04 14:02:46 NONE +++ /project/geometry/cvsroot/geometry/geometry.asd 2007/10/04 14:02:46 1.1 (defsystem :geometry :depends-on () :serial t :components ((:file "geometry")))
--- /project/geometry/cvsroot/geometry/geometry.lisp 2007/10/04 14:02:46 NONE +++ /project/geometry/cvsroot/geometry/geometry.lisp 2007/10/04 14:02:46 1.1 ;; References: http://local.wasp.uwa.edu.au/~pbourke/geometry/
(defpackage :geometry (:use :cl) (:export
;; Dimensions :lerp :lerp2 :clamp ;:square ;:determinant
;; Points :distance :distance-squared
;; Lines/Vectors :scalar-product
:point-is-on-line-p :point-is-on-line-segment-p :lines-are-parallel-p :lines-are-identical-p
:line-intersection :segment-intersection
;; Circles :line-circle-intersection :circle-circle-intersection )) (in-package :geometry)
;; Dimensions (defun lerp (a b u) "Linearly Interpolate from A to B by U. The return value when U = 0 is A, U = 0.5 is halfway from A to B, U = 1 is B." (+ a (* u (- b a))))
(defun lerp2 (a1 b1 a2 b2 u) "Linearly Interpolate from (A1,A1) to (B1,B2) by U." (values (lerp a1 a2 u) (lerp b1 b2 u)))
(defun clamp (min x max) "Clamp x between min and max." (cond ((< x min) min) ((> x max) max) (t x)))
(defun square (x) (* x x))
;;; Compute the determinant ;;; |a b| ;;; | | = ad - bc ;;; |c d| (defun determinant (a b c d) (- (* a d) (* b c)))
;; Points (defun distance (x1 y1 x2 y2) "Return the straight line distance between two points, uses Pythagoras." (sqrt (+ (square (- x1 x2)) (square (- y1 y2)))))
(defun distance-squared (x1 y1 x2 y2) "Return the straight line distance between two points squared, uses Pythagoras." (+ (square (- x1 x2)) (square (- y1 y2))))
;; Lines/Vectors (defun scalar-product (x1 y1 x2 y2) "return the scalar product beween two vectors" (+ (* x1 x2) (* y1 y2)))
(defun point-is-on-line-p (x1 y1 x2 y2 xp yp) "return true iff (xp,yp) is on the line through (x1,y1) and (x2,y2)" (= (* (- xp x1) (- y2 y1)) (* (- yp y1) (- x2 x1))))
(defun point-is-on-line-segment-p (x1 y1 x2 y2 xp yp) "return true iff (xp,yp) is on the line segment between (x1,y1) and (x2,y2)" (and (point-is-on-line-p x1 y1 x2 y2 xp yp) (<= x1 xp x2) (<= y1 yp y2)))
(defun lines-are-parallel-p (x11 y11 x12 y12 x21 y21 x22 y22) "return true iff the lines are parallel" (= (* (- x12 x11) (- y22 y21)) (* (- x22 x21) (- y12 y11))))
(defun lines-are-identical-p (x11 y11 x12 y12 x21 y21 x22 y22) (and (lines-are-parallel-p x11 y11 x12 y12 x21 y21 x22 y22) (point-is-on-line-p x11 y11 x12 y12 x21 y21)))
;;; Return the point of intersection of two lines, one through ;;; (x11,y11) and (x12,y12) and the other one through (x21,y21) ;;; and (x22,y22), as two values, x and y. ;;; ;;; If the lines are the same, return T instead. ;;; ;;; If the lines are parallel but not the same, return NIL instead. ;;; ;;; Mathworld says that to find the intersection of two lines, ;;; one going through (x11,y11) and (x12,y12) and the other one ;;; going through (x21,y21) and (x22,y22) you compute: ;;; ;;; ;;; || x11 y11 | | ;;; || | x11 - x12 | ;;; || x12 y12 | | ;;; | | ;;; || x21 y21 | | ;;; || | x21 - x22 | ;;; || x22 y22 | | ;;; x = -------------------------- ;;; | x11 - x12 y11 - y12 | ;;; | | ;;; | x21 - x22 y21 - y22 | ;;; ;;; ;;; || x11 y11 | | ;;; || | y11 - y12 | ;;; || x12 y12 | | ;;; | | ;;; || x21 y21 | | ;;; || | y21 - y22 | ;;; || x22 y22 | | ;;; y = -------------------------- ;;; | x11 - x12 y11 - y12 | ;;; | | ;;; | x21 - x22 y21 - y22 | ;;; ;;; ;;; This will of course fail when ;;; ;;; | x11 - x12 y11 - y12 | ;;; | | = 0 ;;; | x21 - x22 y21 - y22 | ;;; ;;; If we assume that we are not given degenerate lines with ;;; two identical points, this happens when the lines are parallel. (defun line-intersection (x11 y11 x12 y12 x21 y21 x22 y22) (cond ((lines-are-identical-p x11 y11 x12 y12 x21 y21 x22 y22) t) ((lines-are-parallel-p x11 y11 x12 y12 x21 y21 x22 y22) nil) (t (let* ((dx1 (- x11 x12)) (dx2 (- x21 x22)) (dy1 (- y11 y12)) (dy2 (- y21 y22)) (a (determinant x11 y11 x12 y12)) (b (determinant x21 y21 x22 y22)) (c (determinant dx1 dy1 dx2 dy2))) (values (/ (determinant a dx1 b dx2) c) (/ (determinant a dy1 b dy2) c))))))
;;; Return the point of intersection of two line segments, ;;; (x11,y11) -- (x12,y12) and (x21,y21) -- (x22,y22), ;;; as two values, x and y. ;;; ;;; If there are several points of intersection, return T instead. ;;; ;;; If there are no points of intersection, return NIL instead. ;;; (defun segment-intersection (x11 y11 x12 y12 x21 y21 x22 y22) (multiple-value-bind (x y) (line-intersection x11 y11 x12 y12 x21 y21 x22 y22) (case x ((nil) nil) ((t) (cond ((and (= x11 x21) (= y11 y21) (not (point-is-on-line-segment-p x11 y11 x12 y12 x22 y22)) (not (point-is-on-line-segment-p x21 y21 x22 y22 x12 y12))) (values x11 y11)) ((and (= x11 x22) (= y11 y22) (not (point-is-on-line-segment-p x11 y11 x12 y12 x21 y21)) (not (point-is-on-line-segment-p x21 y21 x22 y22 x12 y12))) (values x11 y11)) ((and (= x12 x21) (= y12 y21) (not (point-is-on-line-segment-p x11 y11 x12 y12 x22 y22)) (not (point-is-on-line-segment-p x21 y21 x22 y22 x11 y11))) (values x12 y12)) ((and (= x12 x22) (= y12 y22) (not (point-is-on-line-segment-p x11 y11 x12 y12 x21 y21)) (not (point-is-on-line-segment-p x21 y21 x22 y22 x11 y11))) (values x12 y12)) (t t))) (t (cond ((and (or (<= x11 x x12) (<= x12 x x11)) (or (<= x21 x x22) (<= x22 x x21)) (or (<= y11 y y12) (<= y12 y y11)) (or (<= y21 y y22) (<= y22 y y21))) (values x y)) (t nil))))))
;; Circles (defun line-circle-intersection (x1 y1 x2 y2 cx cy r) "Return either zero one or two points of intersection between the line (x1,y1) (x2,y2) and the circle centered at (cx,cy) with radius r" (let* ((a (+ (expt (- x2 x1) 2) (expt (- y2 y1) 2))) (b (* 2 (+ (* (- x2 x1) (- x1 cx)) (* (- y2 y1) (- y1 cy))))) (c (- (+ (expt cx 2) (expt cy 2) (expt x1 2) (expt y1 2)) (* 2 (+ (* cx x1) (* cy y1))) (* r r))) (discriminant (- (* b b) (* 4 a c)))) (cond ((= discriminant 0) (multiple-value-call #'lerp2 x1 y1 x2 y2 (/ (- b) 2 a))) ((> discriminant 0) (let* ((d-sqrt (sqrt discriminant)) (u1 (/ (+ (- b) d-sqrt) 2 a)) (u2 (/ (- (- b) d-sqrt) 2 a))) (multiple-value-bind (ix1 iy1) (lerp2 x1 y1 x2 y2 u1) (multiple-value-bind (ix2 iy2) (lerp2 x1 y1 x2 y2 u2) (values ix1 iy1 ix2 iy2))))))))
(defun circle-circle-intersection (x1 y1 r1 x2 y2 r2) "Return the two points of intersection of the two circles circumferences, or nil" (let ((d (distance x1 y1 x2 y2))) (if (<= (abs (- r1 r2)) d (+ r1 r2)) (let* ((a (/ (+ (- (* r1 r1) (* r2 r2)) (* d d)) 2 d)) (h (sqrt (- (* r1 r1) (* a a)))) (x3 (+ x1 (/ (* a (- x2 x1)) d))) (y3 (+ y1 (/ (* a (- y2 y1)) d)))) (values (+ x3 (/ (* h (- y2 y1)) d)) (- y3 (/ (* h (- x2 x1)) d)) (- x3 (/ (* h (- y2 y1)) d)) (+ y3 (/ (* h (- x2 x1)) d)))) (values)))) --- /project/geometry/cvsroot/geometry/mcclim-geom-tests.asd 2007/10/04 14:02:46 NONE +++ /project/geometry/cvsroot/geometry/mcclim-geom-tests.asd 2007/10/04 14:02:46 1.1 (defsystem :mcclim-geom-tests :depends-on (:geometry :mcclim) :serial t :components ((:file "mcclim-geom-tests"))) --- /project/geometry/cvsroot/geometry/mcclim-geom-tests.lisp 2007/10/04 14:02:46 NONE +++ /project/geometry/cvsroot/geometry/mcclim-geom-tests.lisp 2007/10/04 14:02:46 1.1 (defpackage :mcclim-geom-tests (:use :geometry :clim :clim-lisp)) (in-package :mcclim-geom-tests)
(defun geom-test-1 () ;; line intersection (let ((frame (make-application-frame 'geom-test)) p1 p2 p3 p4 l1 l2 i1) (push (setf p1 (make-instance 'geom-point-x-y :x 50 :y 50)) (construction-of frame)) (push (setf p2 (make-instance 'geom-point-x-y :x (- 256 50) :y 50)) (construction-of frame)) (push (setf p3 (make-instance 'geom-point-x-y :x (- 256 50) :y (- 256 50))) (construction-of frame)) (push (setf p4 (make-instance 'geom-point-x-y :x 50 :y (- 256 50))) (construction-of frame)) (push (setf l1 (make-instance 'geom-line-a-b :a p1 :b p3)) (construction-of frame)) (push (setf l2 (make-instance 'geom-line-a-b :a p2 :b p4)) (construction-of frame)) (push (setf i1 (make-instance 'geom-point-line-line-intersection :a l1 :b l2)) (construction-of frame)) (run-frame-top-level frame)))
(defun geom-test-2 () ;; circle and line intersection (let ((frame (make-application-frame 'geom-test)) p1 p2 p3 p4 c1 l1 i1 i2) (push (setf p1 (make-instance 'geom-point-x-y :x 128 :y 128)) (construction-of frame)) (push (setf p2 (make-instance 'geom-point-x-y :x 128 :y (+ 128 60))) (construction-of frame)) (push (setf p3 (make-instance 'geom-point-x-y :x 80 :y 80)) (construction-of frame)) (push (setf p4 (make-instance 'geom-point-x-y :x 200 :y 150)) (construction-of frame)) ;; (push (setf c1 (make-instance 'geom-circle-center-radius :center p1 :point-on-circumference p2)) (construction-of frame)) (push (setf l1 (make-instance 'geom-line-a-b :a p3 :b p4)) (construction-of frame)) ;; (push (setf i1 (make-instance 'geom-point-circle-line-intersection :circle c1 :line l1)) (construction-of frame)) (push (setf i2 (make-instance 'geom-point-circle-line-intersection :circle c1 :line l1 :other t)) (construction-of frame)) (run-frame-top-level frame)))
(defun geom-test-3 () ;; circle and circle intersection (let ((frame (make-application-frame 'geom-test)) p1 p2 p3 p4 c1 c2 i1 i2) (push (setf p1 (make-instance 'geom-point-x-y :x (- 128 50) :y 128)) (construction-of frame)) (push (setf p2 (make-instance 'geom-point-x-y :x 128 :y 128)) (construction-of frame)) (push (setf p3 (make-instance 'geom-point-x-y :x (+ 128 50) :y 128)) (construction-of frame)) (push (setf p4 (make-instance 'geom-point-x-y :x 128 :y (+ 128 50))) (construction-of frame)) ;; (push (setf c1 (make-instance 'geom-circle-center-radius :center p1 :point-on-circumference p2)) (construction-of frame)) (push (setf c2 (make-instance 'geom-circle-center-radius :center p3 :point-on-circumference p4)) (construction-of frame)) ;; (push (setf i1 (make-instance 'geom-point-circle-circle-intersection :left c1 :right c2)) (construction-of frame)) (push (setf i2 (make-instance 'geom-point-circle-circle-intersection :left c1 :right c2 :other t)) (construction-of frame)) (run-frame-top-level frame)))
(defun geom-test-4 () ;; circle from 3 points (let ((frame (make-application-frame 'geom-test)) p1 p2 p3 l1 l2 b1 b2 n1 n2 i1 c1) (push (setf p1 (make-instance 'geom-point-x-y :x 20 :y 120)) (construction-of frame)) (push (setf p2 (make-instance 'geom-point-x-y :x 100 :y 40)) (construction-of frame)) (push (setf p3 (make-instance 'geom-point-x-y :x 180 :y 90)) (construction-of frame)) ;; (push (setf l1 (make-instance 'geom-line-a-b :a p1 :b p2)) (construction-of frame)) (push (setf l2 (make-instance 'geom-line-a-b :a p2 :b p3)) (construction-of frame)) ;; (push (setf b1 (make-instance 'geom-point-bisector :left p1 :right p2)) (construction-of frame)) (push (setf b2 (make-instance 'geom-point-bisector :left p2 :right p3)) (construction-of frame)) ;; (push (setf n1 (make-instance 'geom-line-normal :at b1 :to p1)) (construction-of frame)) (push (setf n2 (make-instance 'geom-line-normal :at b2 :to p2)) (construction-of frame)) ;; (push (setf i1 (make-instance 'geom-point-line-line-intersection :a n1 :b n2)) (construction-of frame)) ;; (push (setf c1 (make-instance 'geom-circle-center-radius :center i1 :point-on-circumference p1)) (construction-of frame)) (run-frame-top-level frame)))
(defclass geom-point () ()) (defclass geom-point-x-y (geom-point) ((x :initarg :x :accessor x-of) (y :initarg :y :accessor y-of))) (defclass geom-point-line-line-intersection (geom-point) ((a :initarg :a :accessor a-of) (b :initarg :b :accessor b-of))) (defclass geom-point-circle-line-intersection (geom-point) ((circle :initarg :circle :accessor circle-of) (line :initarg :line :accessor line-of) (other :initform nil :initarg :other :accessor other))) (defclass geom-point-circle-circle-intersection (geom-point) ((left :initarg :left :accessor left-of) (right :initarg :right :accessor right-of) (other :initform nil :initarg :other :accessor other))) (defclass geom-point-bisector (geom-point) ((left :initarg :left :accessor left-of) (right :initarg :right :accessor right-of)))
(defgeneric point-x-y (geom-point))
(defmethod point-x-y ((point geom-point-x-y)) (values (x-of point) (y-of point)))
(defmethod point-x-y ((point geom-point-line-line-intersection)) (multiple-value-bind (x1 y1) (point-x-y (a-of (a-of point))) (multiple-value-bind (x2 y2) (point-x-y (b-of (a-of point))) (multiple-value-bind (x3 y3) (point-x-y (a-of (b-of point))) (multiple-value-bind (x4 y4) (point-x-y (b-of (b-of point))) (multiple-value-call #'line-intersection x1 y1 x2 y2 x3 y3 x4 y4))))))
(defmethod point-x-y ((point geom-point-circle-line-intersection)) (multiple-value-bind (x1 y1) (point-x-y (a-of (line-of point))) (multiple-value-bind (x2 y2) (point-x-y (b-of (line-of point))) (multiple-value-bind (cx cy cr) (center-and-radius (circle-of point)) (let ((points (multiple-value-list (line-circle-intersection x1 y1 x2 y2 cx cy cr)))) (if (other point) (when (= (length points) 4) (values (elt points 2) (elt points 3))) (when (>= (length points) 2) (values (elt points 0) (elt points 1)))))))))
(defmethod point-x-y ((point geom-point-circle-circle-intersection)) (multiple-value-bind (c1x c1y c1r) (center-and-radius (left-of point)) (multiple-value-bind (c2x c2y c2r) (center-and-radius (right-of point)) (let ((points (multiple-value-list (circle-circle-intersection c1x c1y c1r c2x c2y c2r)))) (when points (if (other point) (values (elt points 2) (elt points 3)) (values (elt points 0) (elt points 1))))))))
(defmethod point-x-y ((point geom-point-bisector))
[89 lines skipped]