Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Алгоритм заполнения треугольника, "Нарисовать" треугольник по трем точкам 
:(
    Опции темы
Чупакабро
Дата 19.2.2009, 21:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 220
Регистрация: 27.2.2007

Репутация: нет
Всего: 4



Есть  массив точек, координаты которых могут быть только целыми.
Даны три точки. Нужно найти точки, принадлежащие треугольнику, заданному этими тремя вершинами.
Так как для меня это не абстрактная задача, а часть конкретной программы на Delphi, то
и описывть ее я буду в терминах этого языка программирования.

Код

type
tdpoint=record
  x,y:integer;
end;
tdpoints=array of tdpoint;


нужно сделать функцию

Код

function GetTrianglePoints(p1,p2,p3:tdpoint):tdpoints;

которая выдает массив точек, находящися внутри треугольника.

Вот... Основное условие - выокая скорость работы. А то у меня всегда получаются громоздкие и медленые алгоритмы.
 
У меня, конечно, есть собственный алгоритм, но чую я ,что он далек от идеала.

Сначала я создаю 3 массива точек, соответствующих отрезкам трегольника.
Потом иду от крайней левой точки к крайней правой (то есть по возрастанию Х), и для каждого Х
выбираю точки, лежащие между ограничивающими сверху и снизу (то есть по У) отрезками.
Это если в двух словах.
Код же будет подлиннее smile :

Код

function GetTrianglePoints(p1,p2,p3:tdpoint):tdpoints;
var
  points:tdpoints;
  line1,line2,line3:tdpoints;//стороны треугольника
  linemax,linemin:tdpoints;//верхняя и нижняя граница треугольника
  lnlength:integer;//количество точек в линии
  xstart:integer;//всомогательная переменная
  xmin,xmax:integer;//координата Х крайней левой и краней правой точек
  curx,curymin,curymax:integer;//текущие координаты
  k,b:single;//коэффициенты в уравнении прямой
  i,j:integer;//счетчик
begin

  //создаем первый отрезок - от точки 1 к точке 2
  if abs(p2.x-p1.x)>abs(p2.y-p1.y) then //эта проверка нужна по нескольким причинам, долго объяснять
  begin
    linelength:=abs(p2.x-p1.x);//находим длину отрезка
    if p2.x>p1.x then xstart:=p1.x else xstart:=p2.x;//находим наименьшее значение Х для отрезка
    k:=(p2.y-p1.y)/(p2.x-p1.x);//ну, кто из школы помнит линейную функцию,
    b:=p1.y-k*p1.x;// тот поймет))
    setlength(line1,lnlength);//устанавливаем длину массива точек первого отрезка
    //находим координаты точек отрезка в цикле
    for i:=xstart to xstart+lnlength-1 do
    begin
      line1[i-xstart].x:=i;
      line1[i-xstart].y:=round(k*i+b);
    end;
    //готово!
  end
  else begin
    if p2.x-p1.x<>0 then
    begin
      linelength:=abs(p2.y-p1.y);//находим длину отрезка
      if p2.y>p1.y then xstart:=p1.y else xstart:=p2.y;//находим наименьшее значение Х для отрезка
      k:=(p2.x-p1.x)/(p2.y-p1.y);
      b:=p1.x-k*p1.y;
      setlength(line1,lnlength);//устанавливаем длину массива точек первого отрезка
      //находим координаты точек отрезка в цикле
      for i:=xstart to xstart+lnlength-1 do
      begin
        line1[i-xstart].y:=i;
        line1[i-xstart].x:=round(k*i+b);
      end;
    end
    else begin  //если 2 точки совпадают
      setlength(line1,1);
      line1[0].x:=p1.x;
      line1[0].y:=p1.y;
    end;
  end;

    //далее аналогично заполняем массивы line2 и line3
    //не привожу здесь код, его и так уже слишком много
    // там p1 и p2 меняются на p2 и p3 и на p3 и p1
    //на выходе у нас 3 отрезка: line1, line2, line3
    // конечно можно было сделать цикл или функцию CreateLine(p1,p2:tdpoint):tdpoints
    // но это не так важно

   //определение крайних точек треугольника
   xmin:=p1.x;
   if p2.x<xmin then xmin:=p2.x;
   if p3.x<xmin then xmin:=p3.x;
   xmax:=p1.x;
   if p2.x>xmax then xmax:=p2.x;
   if p3.x>xmax then xmax:=p3.x;

   curx:=xmin;
   curymin:=p1.y;
   curymax:=p1.y;
   //для каждого X берем максимальный и минимальный Y и заполняем точки между ними
   //результат в linemin и linemax
   repeat
     for i:=0 to length(line1)-1 do
     begin
       if line1[i].x=curx then
       begin
         if line1[i].y<curymin then curymin:=line1[i].y;
         if line1[i].y>curymax then curymax:=line1[i].y;
       end;
     end;
     for i:=0 to length(line2)-1 do
     begin
       if line2[i].x=curx then
       begin
         if line2[i].y<curymin then curymin:=line2[i].y;
         if line2[i].y>curymax then curymax:=line2[i].y;
       end;
     end;
     for i:=0 to length(line3)-1 do
     begin
       if line3[i].x=curx then
       begin
         if line3[i].y<curymin then curymin:=line3[i].y;
         if line3[i].y>curymax then curymax:=line3[i].y;
       end;
     end;
     setlength(linemin,length(linemin)+1);
     setlength(linemax,length(linemax)+1);
     linemin[high(linemin)].x:=curx;
     linemin[high(linemin)].y:=curymin;
     linemax[high(linemax)].x:=curx;
     linemax[high(linemax)].y:=curymax;
     curx:=curx+1;
   until curx=xmax+1;
    
   //переходим к собственно заполнению треугольника
   for i:=0 to length(linemin)-1 do
   begin
     for j:=linemin[i].y to linemax[i].y do
     begin
       setlength(Points,length(Points)+1);
       Points[high(Points)].x:=linemin[i].x;
       Points[high(Points)].y:=j;
     end;
   end;
  result:=points;


end;

Все это работает, но хотелось бы применять менее забористый алгоритм.



Это сообщение отредактировал(а) Чупакабро - 19.2.2009, 21:58
--------------------
Project Project1.exe raised exception class EAccessViolation with message 'Access violation at address 00459B8B in module 'Project1.exe'. Read of address 0000019C'. Process stopped. Use Step or Run to continue.
PM MAIL   Вверх
Akina
Дата 19.2.2009, 23:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 20
Всего: 454



Начни с отсева заведомо НЕ лежащих внутри треугольника точек - это уже сократит перебор.
А затем воспользуйся следующим свойством: если точка лежит снаружи треугольника, то сумма трёх углов, образованных точкой и парами вершин треугольника, менее 360 градусов, если она лежит внутри, то равно 360, если на одной из сторон - один из этих углов 180 градусов.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
Rimch
Дата 20.2.2009, 12:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 43
Регистрация: 23.6.2007

Репутация: 1
Всего: 3



Решение данной задачи на много проще чем вы думаете
Немного теории
1.  Векторное произведение векторов есть площадь //-мма (причем тут вектора станет понятно позже)
2.  Если точка Pi лежит внутри треугольника  P1P2P3, то площадь большого треугольника равна сумме площадей треугольников PiP2P3 + P1PiP3 + P1P2Pi
3.  Векторное произведение векторов есть определитель матрицы составленной из координат векторов
Ща все это на пальцах
1. Что бы узнать где лежит точка Pi  надо найти площадь треугольника  P1P2P3 и сумму площадей  PiP2P3 + P1PiP3 + P1P2Pi, а затем их сравнить.
2. Как найти площадь?
    Оооочень просто
Код

// функция возвращает площадь треугольника по заданным трем точкам 
function TrgSqr(p1,p2,p3:tdpoint):real;
begin
TrgSqr:=abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))/2;
end;

3.  Как узнать лежит ли точка внутри?
   Нефиг делать
Код

// boolean функция возвращающая true/false
// P - точка которую надо проверить, S - площадь большого треугольника ее лучше передавать сразу дабы ускорить время!
function InTrg(P,P1,P2,P3:tdpoint;const S):boolean;
begin
InTrg = (TrgSqr(P1,P2,P3)=(TrgSqr(P,P1,P2)+TrgSqr(P,P1,P3)+TrgSqr(P,P2,P3)));
end;

4. Осталось лишь в цикле перебрать все точки и вывести только те которые удовлетворяют условию  InTrg(P,P1,P2,P3,S)=t
-------------------
Надеюсь вам не составит труда собрать все это в одну кучу, удачи  smile 
PM MAIL ICQ   Вверх
Earnest
Дата 20.2.2009, 12:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5962
Регистрация: 17.6.2005
Где: Рязань

Репутация: 7
Всего: 183



С углами будет дольше: это тригонометрические функции...
Чупакабро, алгоритмы бываю либо эффективные, либо простые. Конечно, есть и исключения, но это как правило.
Я в твоем коде не разбиралась, но то что ты описал на словах, напоминает алгоритм сканирующей линии и является вполне достойным выбором. Хотя в данном случае, как мне представляется (т.е. для треугольника) проверка каждой точки по отдельности методом Жордана особо по быстродействию не проиграет. А вот код будет простой.
И, разумеется, как сказал Akina, для начала нужно отсеять точки, не лежащие внутри экстента треугольника, это быстро.



--------------------
...
PM   Вверх
Чупакабро
Дата 20.2.2009, 16:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 220
Регистрация: 27.2.2007

Репутация: нет
Всего: 4



Цитата(Akina @  19.2.2009,  23:49 Найти цитируемый пост)
Начни с отсева заведомо НЕ лежащих внутри треугольника точек - это уже сократит перебор.

)))) Дело в том, что я ВООБЩЕ не использую здесь перебор. Конечно идея простая как веник - взять прямоугольник, описанный вокруг треугольника и перебирать
все его точки, проверяя на принадлежность треугольнику. Но мы же не ищем простых путей! Не знаю почему, но мне в голову не пришло использовать такой 
простой способ.
Я сравню быстродействие своего алгоритма и перебора и отпишусь.
Rimch, спасибо за алгоритм определения принадлежности точки треугольнику. А то я всегда использовал другой способ: проводил отрезки от точки к вершинам треугольника и проверял их на пересечение со сторонами треугольника. Вот так: изобретаю велосипед, а получается танк с педалями.
А насчет функции TrgSqr: она же выдает вещественное число? А при вычислениях с плавающей точкой неизбежны погрешности. Так что возникает вопрос,
а может ли оказаться так, что сумма трех частей не будет равняться целому? Просто в одной моей программе из-за таких косяков строгие равенства с вещественными числами часто не работали.
Цитата(Earnest @  20.2.2009,  12:37 Найти цитируемый пост)
Я в твоем коде не разбиралась

 smile  А мне вот приходится разбираться в своем коде)) Это очень непросто. Хотя что с меня взять, у меня образование очень далекое от программирования.

А вот мне интересно, как осуществляется закраска треугольников в GPU?


--------------------
Project Project1.exe raised exception class EAccessViolation with message 'Access violation at address 00459B8B in module 'Project1.exe'. Read of address 0000019C'. Process stopped. Use Step or Run to continue.
PM MAIL   Вверх
Earnest
Дата 20.2.2009, 18:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5962
Регистрация: 17.6.2005
Где: Рязань

Репутация: 7
Всего: 183



Тест внутри\снаружи по теореме Жордана имеет меньшую вычислительную сложность чем метод Rimch.
Привожу код на С++ для произвольного полигона; для треугольника можно использовать его же, только теругольник должен описываться 4 точками (первая равна последней):
Код

bool PtInsidePlgn (const CPointD& pt, const polygon& pts)
{
    polygon::const_iterator i = pts.begin(),j = i++;
    bool bInside = false;
    for ( ; i != pts.end(); j = i++) 
    {
        if ((((i->y <= pt.y) && (pt.y < j->y)) || ((j->y <= pt.y) && (pt.y < i->y))) 
                        && (pt.x < (j->x - i->x) * (pt.y - i->y) / (j->y - i->y) + i->x))
            bInside = !bInside;
    }
   return bInside;   
}

На пальцах: проводим через точку горизонтальную прямую и считаем, сколько раз она пересекла стороны треугольника... 

Цитата(Чупакабро @  20.2.2009,  17:45 Найти цитируемый пост)
А вот мне интересно, как осуществляется закраска треугольников в GPU?

Да примерно так же: определяем пересечения каждой линии сканирования со сторонами треугольника и заполняем серединку.


--------------------
...
PM   Вверх
Rimch
Дата 20.2.2009, 18:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 43
Регистрация: 23.6.2007

Репутация: 1
Всего: 3



На счёт ошибок округления, мой студенты часто используют этот метод при решении лабораторных работ, и не разу еще не оказывалось так чтобы ошибка повлеяла на результат принадлежности. В любом методе сидит ошибка округления
PM MAIL ICQ   Вверх
maxim1000
Дата 20.2.2009, 19:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Участник
Сообщений: 3334
Регистрация: 11.1.2003
Где: Киев

Репутация: 33
Всего: 110



касательно погрешностей - раз уж координаты точек целые вполне можно обойтись целочисленной арифметикой, где никаких погрешностей нету

касательно алгоритма в целом - не представляю, чтобы хоть какой-нибудь алгоритм, который делает какие-либо вычисления для каждой точки внутри треугольника обогнал алгоритм, который проводит только вычисления для граничных точек (по сути - немного изменённая версия исходного алгоритма в первом сообщении), т.е. вычисляем граничные точки и идём от меньшей к большей

для начала можно разбить задачу так:
- находим вершину, которая по вертикали находится между двумя другими
- разрезаем треугольник горизонтальной линией на две части
- обрабатываем отдельно обе части (для обоих будет одинаковый алгоритм)

только получившиеся части стоит представлять не в виде трёх вершин, а в виде двух лучей (либо оба вверх, либо оба вниз) и ограничивающей горизонтальной прямой, а то точка разреза не всега будет иметь целые координаты, а значит, начнутся всякие ненужные погрешности


--------------------
qqq
PM WWW   Вверх
Чупакабро
Дата 21.2.2009, 19:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 220
Регистрация: 27.2.2007

Репутация: нет
Всего: 4



Earnest, СРР для меня "иностранный" язык, но, насколько я понял, должен быть массив pts, а его еще нужно заполнить...
А на это тоже тратится время.
С другой стороны, если полигон - треугольник, то нечего в таком случае и перебор устраивать. Все точки от i до j будут внутри.

Сделал я вариант функции с перебором, и вот что у меня получилось:
  - мой старый алгоритм - 188 миллисекунд
  - перебор - 166 миллисекунд.
Но вот в чем фишка: у меня обсчитывается около 18000 треугольников, и у большей части из них координаты всех трех вершин совпадают.
Так что адекватно сравнить скорость работы у меня не получилось.
--------------------
Project Project1.exe raised exception class EAccessViolation with message 'Access violation at address 00459B8B in module 'Project1.exe'. Read of address 0000019C'. Process stopped. Use Step or Run to continue.
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Алгоритмы"

maxim1000

Форум "Алгоритмы" предназначен для обсуждения вопросов, связанных только с алгоритмами и структурами данных, без привязки к конкретному языку программирования и/или программному продукту.


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, maxim1000.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Алгоритмы | Следующая тема »


 




[ Время генерации скрипта: 0.0923 ]   [ Использовано запросов: 20 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.