Главная СПДС СПДС. Функция IS_IN_CONTOUR

;-----------------------------------------------------------------------------------
; Функция проверки (не)вхождения точки в контур, описываемый координатами его вершин
; (C)KAI, 2003,2009 г. (413-2) 65-05-10 Магадан. http://geol-dh.narod.ru/
;-----------------------------------------------------------------------------------
(defun IS_IN_CONTOUR (pt lstpt pres / inters_pt on_edge inside dirflag dirflag_s bcontour xp yp 
                                      ptinfin imax i ptnext ptcurr calc xf yf xs ys yp0 ys0 yf0 intpt)
  ; pt = тестируемая (исходная) точка (координата Z игнорируется!)
  ; lstpt = список точек контура, контур должен содержать более трех точек (координата Z игнорируется!)
  ; pres = точность сравнения точек и координат на равенство (рекомендуется 0.00000000001)

  ; ссылка на функции: NOT_DUPLICATE (Функция исключения дублирующих членов списка, идущих подряд)
  
  ; Проверки должны быть выполнены до вызова функции
  ; Должны проверяться: допустимость типов, количество точек контура, наличие значений
  ; Допускается одно пересечение контура (например, контур в виде восьмерки)
  ; Незамкнутый контур автоматически замыкается.

  ; Благодарность за алгоритм: моей дочери Ольге, Першину Сергею, Зуеву Сергею, а также
  ; Илье Кантору (http://algolist.manual.ru/maths/geom/belong/poly2d.php)
  ; Точка лежит в контуре, если некий луч пересекает отрезки контура нечетное количество раз.
  
  ; В программе используется горизонтальный луч, направленный от точки влево

  ; Одинаковые точки, идущие подряд, в списке не допускаются
  ;   снижаем на порядок точность (чтобы наверняка убрать лишние точки)
  (setq lstpt (NOT_DUPLICATE lstpt (* pres 10)))
  ; Эта проверка на всякий случай (например, на случай наличия сдвоенных точек в списке из 3 точек)
  (if (not (and pt lstpt pres (> (length lstpt) 2)))
    (progn
      (princ (strcat "\n" "No enough data. Не достаточно данных для проверки вхождения точки в контур."))
      (EXIT)
    )
  )
  ; замыкающая точка автоматически добавляется, на концах списка будут абсолютно одинаковые точки
  (if (equal (car lstpt) (last lstpt) pres)
    (setq lstpt (reverse (cdr (reverse lstpt)))); отбрасываем последнюю точку
  )
  (setq lstpt (append lstpt (list (car lstpt)))); добавляем в конец списка первую точку

  (setq imax (length lstpt))
  (setq bcontour T); флаг выхода из цикла (точка задана на отрезке контура или в узле)
  (setq pt (list (car pt) (cadr pt)));переводим в двумерную
  (setq xp (car pt))
  (setq yp (cadr pt))
  (setq ptinfin (list -1.0e7 yp)); левая точка луча. СТРОКА ИЗМЕНЕНА 12.04.2009
  (setq i 0)
  ; Проверяем все отрезки контура, если не встретится случай принадлежности исходной точки
  ;  отрезку контура
  (while (and bcontour (setq ptnext (nth (1+ i) lstpt)));конечная точка текущего отрезка
    (setq ptcurr (nth i lstpt));начальная точка текущего отрезка
    (setq ptcurr (list (car ptcurr) (cadr ptcurr)));переводим точку в двумерную
    (setq ptnext (list (car ptnext) (cadr ptnext)))
    (setq calc T);флаг расчета (анализа)
    (setq xf (car ptcurr))
    (setq yf (cadr ptcurr))
    (setq xs (car ptnext))
    (setq ys (cadr ptnext))
    ; 1. Проверяем, принадлежит ли искомая узлу контура
    (if (or (equal pt ptnext pres)(equal pt ptcurr pres))
      (progn
        (setq calc nil); последующие операторы цикла выполнять не нужно
        (setq bcontour nil); выход из цикла, задача решена
        (setq on_edge "VERTEX"); тип точки на контуре
        (if (equal pt ptnext pres)
          (setq inters_pt ptnext); присваиваем точное значение из списка (а не искомой точки pt)
          (setq inters_pt ptcurr)
        )
      )
    )
    ; 2. Если текущий отрезок контура горизонтален
    (if (and calc (equal yf ys pres))
      (progn
        ;если искомая точка лежит на текущем горизонтальном отрезке
        (if (and (equal yf yp pres) (or (and (<= xf xp) (<= xp xs)) (and (>= xf xp) (>= xp xs))))
          (setq calc nil
                bcontour nil
                on_edge "EDGE"
                inters_pt pt
          );setq
          (progn
            ; если точки отрезка лежит левее искомой точки, но на том же горизонте
            ; p.s. Анализ этого случая в алгоритмах не упоминался, пришлось ввести его
            (if (and (equal yf yp pres) (or (< xf xp) (< xs xp)))
              (progn
                ; флаг направления предыдущего участка сохраняем, вдруг будут несколько
                ;   горизонтальных отрезков подряд
                ; Если предыдущий отрезок не горизонтальный и пересекается лучем (для 1-го участка)
                (if (and (= i 0) 
                         (< xf xp)
                         (not (equal yp (setq yp0 (cadr (nth (- (length lstpt) 2) lstpt))) pres)))
                  (if (< yp0 yp)
                    (setq dirflag "FROM DOWN")
                    (setq dirflag "FROM UP")
                  )
                )
                ; Если предыдущий отрезок не горизонтальный и пересекается лучем
                ;   (для последующих участков)
                (if (and (> i 0) 
                         (< xf xp)
                         (not (equal yp (setq yp0 (cadr (nth (1- i) lstpt))) pres)))
                  (if (< yp0 yp)
                    (setq dirflag "FROM DOWN")
                    (setq dirflag "FROM UP")
                  )
                )
                ; флаг направления последующего участка сохраняем, вдруг будут несколько
                ;   горизонтальных отрезков подряд
                ; Если последующий отрезок не горизонтальный (для последнего участка)
                (if (and (= imax i) (< xf xp) (not (equal yp (setq ys0 (cadr (nth 2 lstpt))) pres)))
                  (progn
                    (if (< ys0 yp)
                      (setq dirflag_s "FROM DOWN")
                      (setq dirflag_s "FROM UP")
                    )
                  )
                )
                ; Если последующий отрезок не горизонтальный
                (if (and (< xf xp) (not (equal yp (setq ys0 (cadr (nth (+ 2 i) lstpt))) pres)))
                  (progn
                    (if (< ys0 yp)
                      (setq dirflag_s "FROM DOWN")
                      (setq dirflag_s "FROM UP")
                    )
                  )
                )
                ; Анализ флагов направления
                (if (and dirflag dirflag_s (/= dirflag dirflag_s))
                  (setq inside (not inside);инвертирование флага пересечения луча контура
                        calc nil
                        dirflag nil dirflag_s nil
                  );setq
                )
              )
              ; луч заведомо не пересекает этот отрезок и далее можно проверок не делать
              (progn
                (setq calc nil); последующие операторы цикла выполнять не нужно
              )
            )  
          )
        )
      )
    )
    ; 3. Анализ случая пересечения луча узла контура по первой точке отрезка
    ;    (вторую точку отрезка проверим на следующей итерации)
    (if (and calc (and (equal yf yp pres) (< xf xp)))
      (progn
        ; для первого отрезка предыдущей точкой будет предпоследняя точка списка
        (if (= i 0)
          (setq yf0 (cadr (nth (- (length lstpt) 2) lstpt)))
          (setq yf0 (cadr (nth (1- i) lstpt)))
        )
        ;вершины по разные стороны от луча?
        (if (or (and (and (> yf0 yp) (< ys yp)) (not (equal yf0 yp pres)))
                (and (and (< yf0 yp) (> ys yp)) (not (equal yf0 yp pres))))
          (setq inside (not inside);инвертирование флага пересечения луча контура
                calc nil
          );setq
        )
      )
    )
    ; 4. Остается проверить случай, когда луч пересекает наклонный отрезок между вершинами
    (if (and calc (setq intpt (inters ptnext ptcurr pt ptinfin)))
      (progn
        (if (equal intpt pt pres); если точка пересечения точно на отрезке
          (setq calc nil; to skip next operators
                bcontour nil ;end of while
                on_edge "EDGE"
                inters_pt intpt
          );setq
          (progn
            (if (not (or (and (equal ys yp pres) (< xs xp)) (and (equal yf yp pres) (< xf xp))))
              (setq inside (not inside));инвертирование флага пересечения луча контура
            )
          )
        )
      )
    )
    (setq i (1+ i))
  );while
  
  ; если точка лежит на контуре или в узле, значит она принадленит контуру
  ; устанавливаем принудительно значение флага четности пересечений
  (if inters_pt
    (setq inside T)
  )
  ; точка истинно будет внутри контура при inters_pt=nil (анализировать при необходимости)
  
  ;возврат значений списком из:
  ; 1. флага вхождения точки в контур [nil or T]
  ; 2. Координаты точки на контуре (узла контура) или nil [список координат].
  ;    Координаты искомой точки заменяются координатами узла или найденной точки пересечения
  ;    (за исключением пересечения на горизонтальном участке).
  ; 3. Расшифровка где именно лежит точка на контуре [строка], выдаваемые значения: "VERTEX" либо "EDGE"
  (list inside inters_pt on_edge)
);end of ******** IS_IN_CONTOUR *******

  при полном или частичном использовании материалов сайта ссылка на источник обязательна ©2002-2012

Hosted by uCoz