admin 发表于 2024-1-27 07:11:19

【越飞越高讲堂12】最小包围盒和最大距离点对 - AutoLISP/Visual LISP 编程

最小包围盒和最大距离点对在实际生活中有很多运用。
譬如,最小包围盒指的是能包围一个多边形或者一些多边形的最小面积(可能周长)的这样的一个矩形,这个帖子我只是讨论二维的,所以说是矩形,
求得最小包围和就能合理利用材料,选择合适的截面。
在这个算法中,采用了“游标卡尺”的算法,所以运行速度很快,在求出一个形状的凸包之后,就能迅速得到最小矩形。O(n *log(n))的时间复杂度,
大量运算成为可能。
对于CAD本身来说,用getboundingBox得出的结果仅仅是平行与WCS的坐标系统的矩形,而且有时候很不准确,特别对于spline来说,有时候相差很大。
然而,WCS下的boundingBox一般来说不是最小矩形,如果要得到最小的,必须要旋转这个图形很多次才能得到结果,但这时一个费事费力的事情。
所以,我们有必要考虑一种算法。
这些天通过几个比较晚的晚上,终于得出了算法。为了增加讨论效果,我在这里先贴出arx文件,适用于2004-2006平台。至于LISP,以后在贴出。
速度之快,几乎是0秒。

用法: 先输入命令 Hull,
然后输入分弧精度---指的是对样条曲线或者弧形的分段数目,取值在100-2000比较合理,太大容易引起问题且也不能有效提高精度。
选择图形中的多边形或者样条曲线,然后你就可以看到结果了。最远距离用红线标出。


;;;The procedure for Test
(defun C:test (/ PP PTLIST SEL T0 n)
(princ "\n命令是test(The command is Test).")
(princ "\nPlease select LWPOLYLINE,LINE,SPLINE,POINT")
(setq sel (ssget (list '(0 . "POINT,LWPOLYLINE,LINE,SPLINE"))))         ;select curve or point
(initget 7)
(setq n (getint "\nPlease Enter the precision of dividing curve(The resonable value is 100 to 2000):"))
(if sel                                                         
    (progn
      (setq ptlist (getpt sel n))                                        ;construct the set of points
      (setq ptlist (Graham-scan ptlist))                              ;construct the CCW Hull of this set.
      (if (<= (det (car ptlist) (cadr ptlist) (caddr ptlist)) 0.0)      ;ensure the hull is CCW.
      (setq ptlist (reverse ptlist))                                        ;if it isn't CCW,then reverse it
      )      
      (setq t0 (getvar "TDUSRTIMER"))                                        ;The start time of this algorithm
      (setq pp (car (MinAreaRectangle ptlist)))                              ;start calculating
      (princ "\nIt takes :")                                                
      (princ (* (- (getvar "TDUSRTIMER") t0) 86400))                         ;The End time
      (princ "seconds")
      (if pp
      (make-poly pp)                                                      ;draw rectangle.
      )
    )
)
(princ)
)
;;;=======================================================
;;;Function : Find the minimum area of encasing rectangle.
;;;Arguments : A CCW HULL                                 
;;;Return: The Four points of Rectangle and its Area      
;;;=======================================================
(defun MinAreaRectangle      (ptlist             /         AA         AI    BB    D1
                         D2    EDGEI         I1X         I1Y   I2X   I2Y
                         IL    INF   IX         IY         J1    J2    MINA
                         MINHMINWNORHNORM         PI1   PI2   PTI0
                         PTI1PTI2PTJ1PTK1         PTM1PTS1PTS2
                         PTS3PTS4REC1REC2         REC3REC4RECT
                         VECHVECLVJ12VM12
                        )
(setq INF 1e309)                                                      
(setq minA INF)                                                      ;Initiating the Minimum area is infinite
(setq pti0 (car ptlist))                                                ;the first point of Hull.
(setq pts1 (append ptlist (list pti0)))                              ;add the first point at back of Hull
(setq pts2 (cdr (append ptlist ptlist (list pti0))))                        ;Construct a loop for the hull
(setq i 0)                                                               

;;Find area of encasing rectangle anchored on each edge.
(repeat (length ptlist)
    (setq pi1 (car   pts1)                                                
          pi2 (cadrpts1)
          i1x (car   pi1)
          i1y (cadrpi1)
          i2x (car   pi2)
          i2y (cadrpi2)
          ix(- i2x i1x)
          iy(- i2y i1y)
          il(distance (list ix iy) '(0.0 0.0))
    )

    ;;寻找最左点
    ;;Find a vertex on on first perpendicular line of support
    (while (> (DOTPR ix iy pts2) 0.0)
      (setq pts2 (cdr pts2))
    )

    ;;寻找最上点
    ;;Find a vertex on second perpendicular line of suppoer
    (if      (= i 0)
      (setq pts3 pts2)
    )
    (while (> (CROSSPR ix iy pts3) 0.0)
      (setq pts3 (cdr pts3))
    )

    ;;寻找最右点
    ;;Find a vertex on second perpendicular line of suppoer
    (if      (= i 0)
      (setq pts4 pts3)
    )
    (while (< (DOTPR ix iy pts4) 0.0)
      (setq pts4 (cdr pts4))
    )

    ;;得出了每边的矩形
    ;;Find distances between parallel and perpendicular lines of support
    (cond
      ((equal i1x i2x 1e-4)                                                ;如果边两点的X值相同
       (setq d1      (- (caar pts3) i1x)                                        ;那么矩形的高就是最上点与边的X的差值
             d2      (- (cadar pts4) (cadar pts2))                              ;矩形的宽就是最左和最右的Y的差值
       )
      )
      ((equal i1y i2y 1e-4)                                                ;如果边两点的Y值相同
       (setq d1      (- (cadar pts3) i1y)                                        ;那么矩形的高就是最上点与边的Y的差值
             d2      (- (caar pts4) (caar pts2))                              ;矩形的宽就是最左和最右的X的差值
       )
      )

      (T
       (setq aa (det pi1 pi2 (car pts3)))                              ;否则计算边和最上点构成的面积的二倍(det)
       (setq d1 (/ aa il))                                                ;高就是det值除以边长
       (setq j1 (car pts2))                                                ;最右边点
       (setq j2 (list (- (car j1) iy) (+ (cadr j1) ix)))                ;通过最右边点的垂直边的点
       (setq bb (det j1 j2 (car pts4)))                                        ;最右边点,上面的点和最左边的点
       (setq d2 (/ bb il))                                                ;这三点的det除以边长就是宽
      )
    )

    ;;计算矩形的面积,必要时更新最小面积
    ;;Compute area of encasing rectangle anchored on current edge.
    ;;if the area is smaller than the old Minimum area,then update,and record the width,height and five points.
    (setq Ai (abs (* d1 d2)))                                                ;面积就是高和宽的积
    (if      (< Ai MinA)                                                   ;如果面积小于先前的最小面积,则记录:
      (setq MinA Ai                                                      ;更新最小面积
            MinH d1                                                      ;最小面积的高
            MinW d2                                                      ;最小面积的宽
            pti1 pi1                                                      ;最小面积的边的第一个端点
            pti2 pi2                                                      ;最小面积的边的第二个端点
            ptj1 (car pts2)                                                ;最右边的点
            ptk1 (car pts3)                                                ;最上面的点
            ptm1 (car pts4)                                                ;最左边的点
      )
    )
    (setq pts1 (cdr pts1))                                                ;检测下一条边
    (setq i (1+ i))                                                      ;计数器加一
)

;;according to the result ,draw the Minimum Area Rectangle
(setq edge (mapcar '- pti2 pti1))                                        ;最小面积的边对应的向量
(setq VecL (distance edge '(0.0 0.0)))                              ;最小面积的边的长度
(setq NorH (abs (/ MinH VecL)))                                        ;这边的法线

(setq Norm (list (- (cadr edge)) (car edge)))                              ;这边的垂直向量
(setq vj12 (mapcar '+ ptj1 Norm))                                        ;通过最右点的垂直向量
(setq vm12 (mapcar '+ ptm1 Norm))                                        ;通过最左点的垂直向量
(setq vecH (mapcar '* (list NorH NorH) Norm))                              

(setq rec1 (inters pti1 pti2 ptj1 vj12 nil))                              ;矩形的第一点
(setq rec4 (inters pti1 pti2 ptm1 vm12 nil))                              ;矩形的第四点
(setq rec2 (mapcar '+ rec1 vecH))                                        ;矩形的第二点
(setq rec3 (mapcar '+ rec4 vecH))                                        ;矩形的第三点
(setq rect (list Rec1 rec2 rec3 rec4))                              ;矩形的点表
(cons rect MinA)                                                      ;返回这个矩形的点表和最大距离
)

;;;========================================
;;;求凸壳的直径的程序                     
;;;参数:逆时针的凸壳 H-------注意逆时针!!!
;;;返回值: 直径的两个端点和直径 Pair . MaxD
;;;========================================
(defun Max-distance (H / D M MAXD P PAIR Q U V W)
(setq Q (cdr (append H H (list (car H)))))                              ;构造一个首尾循环的凸集,且起始点为凸壳的第二点
(setq MaxD 0.0)                                                      ;初始化最小距离为0
(foreach U H                                                                ;依次检查凸壳的边
    (setq V (car Q))                                                      ;循环集的第一点
    (setq W (cadr Q))                                                      ;循环集的第二点
    (setq M (mid-pt V W))                                                ;这两点的中点
    (while (> (dot M U V) 0.0)                                                ;如果夹角小于90度(即点积大于0)
      (setq Q (cdr Q))                                                      ;循环集推进
      (setq V (car Q))                                                      ;取下一点
      (setq W (cadr Q))                                                      ;下下一点
      (setq M (mid-pt V W))                                                ;这两点的中点
    )
    (setq D (distance U V))                                                ;计算这时的最大距离
    (if      (> D MaxD)                                                      ;如果大于前面的最大距离
      (setq MaxD D                                                      ;就替换前面的最大距离
            Pair (list U V)                                                ;并记录这对点
      )
    )
)
(cons Pair MaxD)                                                      ;返回这对点和最大距离
)

;;;==================
;;;Graham扫描法求凸包
;;;==================
(defun Graham-scan (ptlist / hullpt maxXpt sortPt P Q)
(if (< (length ptlist) 3)                                                ;3点以下
    ptlist                                                                ;是本集合
    (progn
      (setq maxXpt (assoc (apply 'max (mapcar 'car ptlist)) ptlist))      ;最右边的点
      (setq sortPt (sort-by-angle-distance ptlist maxXpt))               ;分类点集
      (setq hullPt (list (cadr sortPt) maxXpt))                              ;开始的两点      
      (foreach n (cddr sortPt)                                                ;从第3点开始
      (setq hullPt (cons n HullPt))                                        ;把Pi加入到凸集
      (setq P (cadr hullPt))                                                ;Pi-1
      (setq Q (caddr hullPt))                                                ;Pi-2
      (while (and q (> (det n P Q) -1e-6))                                 ;如果左转
          (setq hullPt (cons n (cddr hullPt)))                                 ;删除Pi-1点
          (setq P (cadr hullPt))                                        ;得到新的Pi-1点
          (setq Q (caddr hullPt))                                        ;得到新的Pi-2点
      )
      )
      (reverse hullpt)                                                      ;返回凸集
    )
)
)

;;;中点函数
(defun mid-pt (p1 p2)
(list
    (* (+ (car p1) (car p2)) 0.5)
    (* (+ (cadr p1) (cadr p2)) 0.5)
)
)

;;;以某点为基点,按照角度和距离分类点集
(defun sort-by-angle-distance (ptlist pt / )
(vl-sort ptlist
         (function
             (lambda (e1 e2 / ang1 ang2 )
               (setq ang1 (angle pt e1))
               (setq ang2 (angle pt e2))
               (if (= ang1 ang2)
               (< (distance pt e1) (distance pt e2))
               (< ang1 ang2)
               )
             )
         )
)
)

;;;点积= x1*x2 + y1*y2
(defun DOTPR (ix iy pts / pt1 pt2)
(setq pt1 (car pts))
(setq pt2 (cadr pts))
(+ (* ix (- (car pt2) (car pt1)))
   (* iy (- (cadr pt2) (cadr pt1)))
)
)

;;;叉积= x1*y2 - x2*y1
(defun CROSSPR (ix iy pts / pt1 pt2)
(setq pt1 (car pts))
(setq pt2 (cadr pts))
(- (* ix (- (cadr pt2) (cadr pt1)))
   (* iy (- (car pt2) (car pt1)))
)
)

;;;中点函数
(defun mid-pt (p1 p2)
(list
    (* (+ (car p1) (car p2)) 0.5)
    (* (+ (cadr p1) (cadr p2)) 0.5)
)
)

;;;定义三点的行列式,即三点之倍面积
(defun det (p1 p2 p3 / x2 y2)
(setq      x2 (car p2)
      y2 (cadr p2)
)
(- (* (- x2 (car p3)) (- y2 (cadr p1)))
   (* (- x2 (car p1)) (- y2 (cadr p3)))
)
)

;;;定义向量的点积函数
(defun dot (p1 p2 p3 / x1 y1)
(setq      x1 (car p1)
      y1 (cadr p1)
)
(+ (* (- (car p2) x1) (- (car p3) x1))
   (* (- (cadr p2) y1) (- (cadr p3) y1))
)
)
;;;取点函数2
(defun getpt (ss n / i s a b c d e)
(setq i 0)
(if ss
    (repeat (sslength ss)
      (setq a (ssname ss i))
      (setq b (entget a))
      (setq e (cdr (assoc 0 b)))
      (cond
      ((= e "LWPOLYLINE")
         (setq c (get-pline-vertexs a n))
         (setq s (append c s))
      )
      ((= e "SPLINE")
         (setq c (get-spline-vertexs a n))
         (setq s (append c s))
      )
      ((= e "LINE")
         (setq c (cdr (assoc 10 b)))
         (setq d (cdr (assoc 11 b)))
         (setq c (list (car c) (cadr c)))
         (setq d (list (car d) (cadr d)))
         (setq s (cons c s))
         (setq s (cons d s))
      )
      ((= e "POINT")
         (setq c (cdr (assoc 10 b)))
         (setq c (list (car c) (cadr c)))
         (setq s (cons c s))
      )
      )
      (setq i (1+ i))
    )
)
s
)

;;取得多边形顶点
(defun get-LWpolyline-vertexs (DXF / lst)
(foreach n DXF
    (if      (= (car n) 10)
      (setq lst (cons (cdr n) lst))
    )
)
(reverse lst)
)

(defun get-3dpolyline-vertexs ( ent / p )
(if (and (setq ent (entnext ent)) (setq p (cdr (assoc 10 (entget ent)))))
    (cons p (get-3dpolyline-vertexs ent))
)
p
)

;;;取得样条曲线的点
(defun get-spline-vertexs (ent n / DIST ENDPAR I LEN OBJ PT PTS SEG)
(setq obj (vlax-ename->vla-object ent))
(setq endpar(vlax-curve-getEndParam obj))
(setq len (vlax-curve-getDistAtParam obj endpar))
(setq seg (/ len n))
(setq dist 0)
(while (< dist len)   
    (setq pt (vlax-curve-getPointAtDist obj dist))
    (setq pts (cons pt pts))
    (setq dist (+ seg dist))   
)
(if (= (vla-get-closed obj) :vlax-false)
    (setq pt (vlax-curve-getEndPoint obj)
          pts (cons pt pts)
    )
)
(reverse pts)
)

;;;取得含有圆弧的多段线的点
(defun get-pline-vertexs (ent n / BLG DIST ENDPAR I L1 L2 L3 LI OBJ PT PTS VEXNUM)
(setq obj (vlax-ename->vla-object ent))
(setq endpar (vlax-curve-getEndParam obj))
(setq vexNum (fix endPar))
(setq pts nil)
(setq i 0)
(repeat vexNum
    (setq pt (vlax-curve-getPointAtParam obj i))
    (setq pts (cons pt pts))
    (setq blg (vla-getbulge obj i))
    (if (/= blg 0.0)
      (progn
      (setq l1 (vlax-curve-getDistAtParam obj i))
      (setq l2 (vlax-curve-getDistAtParam obj (1+ i)))
      (setq l3 (- l2 l1))
      (setq li (/ l3 n))
      (setq dist l1)
      (repeat (1- n)
          (setq dist (+ dist li))
          (setq pt (vlax-curve-getPointAtDist obj dist))
          (setq pts (cons pt pts))
      )
      )
    )
    (setq i (1+ i))
)
(if (= (vla-get-closed obj) :vlax-false)
    (setq pt (vlax-curve-getEndPoint obj)
          pts (cons pt pts)
    )
)
pts
)

;;;绘制多段线
(defun Make-Poly (pp / x)
(entmake                                                                ;画凸包
    (append
      '((0 . "LWPOLYLINE")
      (100 . "AcDbEntity")
      (100 . "AcDbPolyline")
       )
      (list (cons 90 (length pp)))                                        ;顶点个数
      (mapcar
      (function (lambda (x) (cons 10 x)))
      pp
      )                                                                        ;多段线顶点
      (list (cons 70 1))                                                ;闭合的
      (list (cons 62 1))                                                ;红色的
    )
)
)

admin 发表于 2024-1-27 09:53:38

;;;========================================
;;;求凸壳的直径的程序                     
;;;参数:逆时针的凸壳 H-------注意逆时针!!!
;;;返回值: 直径的两个端点和直径 Pair . MaxD
;;;========================================
(defun Max-distance (H / D M MAXD P PAIR Q U V W)
(setq Q (cdr (append H H (list (car H))))) ;构造一个首尾循环的凸集,且起始点为凸壳的第二点
(setq MaxD 0.0)    ;初始化最小距离为0
(foreach U H   ;依次检查凸壳的边
    (setq V (car Q))    ;循环集的第一点
    (setq W (cadr Q))    ;循环集的第二点
    (setq M (mid-pt V W))   ;这两点的中点
    (while (> (dot M U V) 0.0)   ;如果夹角小于90度(即点积大于0)
      (setq Q (cdr Q))    ;循环集推进
      (setq V (car Q))    ;取下一点
      (setq W (cadr Q))    ;下下一点
      (setq M (mid-pt V W))   ;这两点的中点
    )
    (setq D (distance U V))   ;计算这时的最大距离
    (if (> D MaxD)    ;如果大于前面的最大距离
      (setq MaxD D    ;就替换前面的最大距离
   Pair (list U V)   ;并记录这对点
      )
    )
)
(cons Pair MaxD)    ;返回这对点和最大距离
)
页: [1]
查看完整版本: 【越飞越高讲堂12】最小包围盒和最大距离点对 - AutoLISP/Visual LISP 编程