Poligoane GEOMETRIE computaționalăAngel Proorocu Ne propunem să prezentăm un algoritm de determinare a intersecției a două poligoane convexe. Această problemă a fost tratată în GInfo nr 4-5/1998, dar algoritmul prezentat atunci avea complexitatea O(n*m). Vom prezenta acum un algoritm de complexitate liniară, respectiv de O(n+m). Problemă Fie în planul P două poligoane convexe P și Q, cu n și respectiv m puncte. Fiind date aceste poligoane, să se găsească intersecția lor.
Algoritmul pe care îl vom prezenta este util în rezolvarea multor probleme, ca de exemplu în determinarea faptului că două mulțimi de puncte sunt separabile printr-o dreaptă. Prima rezolvare liniară a fost găsită de Shamos (1978), dar noi vom prezenta algoritmul introdus în 1982 de O'Rouke, Chien, Olson și Naddor. Ideea de bază a algoritmului este simplă, dar implementarea este delicată, cum se întâmplă de obicei la problemele de geometrie analitică. Înainte de prezentarea algoritmului, trebuie să subliniem un aspect evident dar foarte important: intersecția a două poligoane convexe este vidă sau este un poligon convex. Presupunând că punctele poligoanelor sunt date în ordinea inversă acelor de ceasornic nu restrângem generalitatea algoritmului.
Fig. 1. Poligonul P și deplasarea vectorului A
Fie A și B doi vectori care se suprapun, A pe una din laturile lui P și B pe una din laturile lui Q, ca în figura 1. În algoritm, vectorii A și B se deplasează pe conturul poligoanelor P și Q în sens trigonometric astfel încât se suprapun în fiecare punct de intersecție al poligoanelor. La început, vectorii sunt suprapuși pe orice latură. În continuare trebuie să decidem la fiecare pas care vector trebuie deplasat.
Fig. 2. Putem scrie AźB, dar nu putem scrie BźA, deaorece A indică spre B
Notăm cu AźB faptul că prelungirea vectorului A în sensul de deplasare întâlnește dreapta suport a lui B; notăm analog cu BźA faptul că prelungirea vectorului B în sensul de deplasare întâlnește dreapta suport a lui A (figura 2). Mai notăm cu H(A) semiplanul din stânga lui A, H(B) semiplanul din stânga lui B, b punctul din "vârful" lui B și a punctul din "vârful" lui A.
Cu notațiile descrise, condițiile de deplasare se pot scrie astfel: 1) AźB Ț deplasăm vectorul A 2) BźA Ț deplasăm vectorul B 3) AźB și BźA Ț deplasăm oricare dintre vectorii A și B 4) nu este îndeplinită nici una din condițiile AźB și BźA Ț dacă bÎH(A) atunci avansăm A, altfel avansăm B.
Algoritmul în pseudocod este următorul:
Alege A și B oarecare inside Î {unknown,Pin,Qin} { arată dacă un poligon este inclus în celălalt }
repetă dacă A se intersectează cu B atunci se modifică variabila inside; punctul de intersecție este adăugat poligonului soluție; se avansează A sau B în funcție de condițiile de mai sus; punctul din "coada" vectorului avansat este adăugat poligonului soluție dacă variabila inside arată că poligonul pe care se deplasează vectorul avansat este în interior; până când nr_iterații>2(n+m);
Se tratează particular cazurile: PÇQ=Æ, PÍQ, QÍP.
Notând cu AŽB produsul vectorial al lui A și B, condițiile geometrice de deplasare pot fi scrise astfel în funcție de semnul lui AŽB și de valoarea de adevăr a propozițiilor aÎH(B) și bÎH(A):
AŽB aÎH(B) bÎH(A) Vectorul deplasat >0 T T A >0 T F A sau B >0 F T A >0 F F B <0 T T B <0 T F B <0 F T A sau B <0 F F A
Echivalența între aceste condiții și cele expuse mai sus s-ar putea să nu fie foarte clară la prima vedere, însă spațiul nu ne permite o demonstrație riguroasă. Algoritmul este însă intuitiv și, după rularea câtorva exemple, vă veți convinge de acuratețea formulelor.
Programul Pascal, care realizează vizualizarea grafică a algoritmului, este prezentat în continuare. Lăsăm pe seama cititorului tratarea cazurilor speciale PÇQ=Æ, PÍQ, QÍP.
Programul citește din fișierul INPUT.TXT două poligoane în următorul format: l prima linie conține numărul de puncte n ale primului poligon; l următoarele n linii conțin perechi de numere reale Xi, Yi (i=1..n) reprezentând coordonatele punctelor poligonului în ordine trigonometrică; l a n+2-a linie conține numărul de puncte m pentru al doilea poligon; l următoarele M linii conțin perechi de coordonate reale Xi, Yi (i=1..n) reprezentând coordonatele punctelor poligonului în ordine trigonometrică.
Programul afișează pe ecran cele două poligoane, apoi vizualizează pașii algoritmului. Poligoanele sunt desenate cu verde, respectiv roșu, iar vectorii A și B cu alb, respectiv galben. La fiecare apăsare a tastei ENTER, se avansează cu unul dintre cei doi vectori, iar după terminarea algoritmului se vizualizează intersecția celor două poligoane. Funcțiile și procedurile programului sunt explicate în comentarii.
Listing: INTER.PAS {$M 65384,0,655360} Program IntersectiePoligoane; uses Graph,Crt; type TPoint=array[0..1]of Real; { contine coordonatele x, y ale unui punct } Tinflag=(Pin,Qin,unknown); { ne indica daca unul din poligoane } { este in interiorul celuilalt } TSegment=array[0..1]of TPoint; { un segment-contine doua puncte } TIntPoint=array[0..1]of Integer; { un punct cu coordonate intregi } { pentru afisarea pe ecran } TPolyPoints=array[0..1000]of TIntPoint; { poligon cu coordonate intregi } { pentru afisarea pe ecran } var n,m:Integer; { nr de puncte ale poligoanelor P si Q } PolyPoints:TPolyPoints; minx,maxx,maxy,miny:Real; P,Q,Sol:array[0..1000]of TPoint; { poligoanele } f:Text; inflag:Tinflag; { indicatorul ce poate fi } { Pin, Qin sau unknown } gd,gm,na,mb,a,b,a1,b1,ns:Integer; AB,BA:Boolean;
procedure Linie(x,y,xx,yy:Real); { deseneaza o linie pe ecran }
begin { se convertesc coordonatele reale la } { coordonate ecran, in functie de } { minimul/maximul pe x si pe y } x:=639*(1-(maxx-x)/(maxx-minx)); xx:=639*(1-(maxx-xx)/(maxx-minx)); y:=479*(1-(maxy-y)/(maxy-miny)); yy:=479*(1-(maxy-yy)/(maxy-miny)); Line(Trunc(x),479-Trunc(y),Trunc(xx), 479-Trunc(yy)) end;
procedure ReadData; { citeste datele,initializeaza modul grafic } var i,j,grDriver,ErrCode,grMode:Integer; begin Assign(f,'INPUT.TXT'); Reset(f); Readln(f,n); for i:=0 to n-1 do Readln(f,P[i][0],P[i][1]); minx:=Maxlongint; maxx:=-Maxlongint; miny:=Maxlongint; maxy:=-Maxlongint; { se determina minimul si maximul coord. } { punctelor din poligoane pentru functia de } { convertire de la real la coord. ecran } for i:=0 to n-1 do begin if p[i][0]<minx then minx:=p[i][0]; if p[i][0]>maxx then maxx:=p[i][0]; if p[i][1]<miny then miny:=p[i][1]; if p[i][1]>maxy then maxy:=p[i][1] end; Readln(f,m); for i:=0 to m-1 do Read(f,Q[i,0],Q[i,1]); for i:=0 to m-1 do begin if q[i][0]<minx then minx:=q[i][0]; if q[i][0]>maxx then maxx:=q[i][0]; if q[i][1]<miny then miny:=q[i][1]; if q[i][1]>maxy then maxy:=q[i][1] end; Close(f); a1:=0; b1:=0; a:=(a1+1) mod n; b:=(b1+1) mod n; inflag:=unknown; { la inceput nu se stie care poligon } { este in interiorul celuilalt } na:=0; mb:=0; grDriver:=Detect; InitGraph(grDriver, grMode,'c:\bp\bgi'); SetColor(green); for i:=0 to n-1 do begin j:=(i+1)mod n; Linie(p[i][0],p[i][1],p[j][0],p[j][1]) end; SetColor(red); for i:=0 to m-1 do begin j:=(i+1)mod m; Linie(q[i][0],q[i][1],q[j][0],q[j][1]) end end;
function Arie(a,b,c:Tpoint):Real; { calculeaza aria*2 a unui triunghi,semnul } { functiei depinde de ordinea punctelor: } { exista o echivalenta intre semnul acestei } { functii si produsul vectorial AxB } begin Arie:=a[0]*b[1]-a[1]*b[0]+b[0]*c[1]- c[0]*b[1]+c[0]*a[1]-a[0]*c[1]; end;
procedure WriteSol; var i:Integer;
begin Assign(f,'output.txt'); Rewrite(f); for i:=1 to ns do Writeln(f,sol[i][0]:0:3, ' ',sol[i][1]:0:3); Close(f) end; function Stanga(A:TSegment; pB:TPoint):Boolean; begin { returneaza true numai daca punctul } { pB este in stanga segmentului A } Stanga:=(Arie(A[0],A[1],pB)>0) end;
function Intersect(A,B:TSegment; var P:TPoint):Boolean; var s,t,det:Real;
begin { calculeaza intersectia segmentelor A si B } { returneaza true numai daca segmentele se } { intersecteaza; nu vom detalia procedura } { deoarece o metoda similara a facut } { subiectul unui articol anterior in GInfo } det:= A[0][0]*(B[1][1]-B[0][1])+ A[1][0]*(B[0][1]-B[1][1])+ B[1][0]*(A[1][1]-A[0][1])+ B[0][0]*(A[0][1]-A[1][1]); if det=0 then begin Intersect:=false; Exit end; s:=( A[0][0]*(B[1][1]-B[0][1])+ B[0][0]*(A[0][1]-B[1][1])+ B[1][0]*(B[0][1]-A[0][1]))/det; t:=-( A[0][0]*(B[0][1]-A[1][1])+ A[1][0]*(A[0][1]-B[0][1])+ B[0][0]*(A[1][1]-A[0][1]))/det; P[0]:=A[0][0]+S*(A[1][0]-A[0][0]); P[1]:=A[0][1]+S*(A[1][1]-A[0][1]); Intersect:=(0.0<=s)and(s<=1.0)and(0.0<=t) and(t<=1) end;
function SgnArieOrg(A,B:TSegment):Integer; var p1,p2,p3:TPoint; aux:Real;
begin { calculeaza semnul produsului vectorial AxB } p1[0]:=0; p1[1]:=0; p2[0]:=A[1][0]-A[0][0]; p2[1]:=A[1][1]-A[0][1]; p3[0]:=B[1][0]-B[0][0]; p3[1]:=B[1][1]-B[0][1]; aux:=Arie(p1,p2,p3); if aux=0 then SgnArieOrg:=0 else if aux>0 then SgnArieOrg:=1 else SgnArieOrg:=-1 end;
procedure Avanseaza(var c,a,a1,n:Integer; inside:Boolean; P:TPoint);
begin { avanseaza vectorul de pe poligonul } { precizat prin parametri } Inc(c); if inside then begin Inc(ns); sol[ns]:=P end; a1:=a; a:=(a+1) mod n end;
procedure Rezolva; var pP:TPoint; AA,BB:TSegment; cross_over:Boolean; i,Sign:Integer; begin { numarul de puncte ale polig. solutie } ns:=-1; repeat { AA si BB sunt vectorii ce se } { ataseaza poligoanelor } AA[0]:=P[a1]; AA[1]:=P[a]; Setcolor(white); linie(aa[0,0],aa[0,1],aa[1,0],aa[1,1]); BB[0]:=Q[b1]; BB[1]:=Q[b]; Setcolor(yellow); linie(bb[0,0],bb[0,1],bb[1,0],bb[1,1]); Readln; AB:=Stanga(AA,BB[1]); BA:=Stanga(BB,AA[1]); cross_over:=Intersect(AA,BB,pP); Sign:=SgnArieOrg(AA,BB); if cross_over then { daca vectorii se intersecteaza } begin if inflag=unknown then begin na:=0; mb:=0 end; Inc(ns); Sol[ns]:=pP; if BA then inflag:=Pin else inflag:=Qin { se seteaza flagul ce arata care poligon } { este inautru, interior fiind poligonul } { care este in stanga celuilalt, deoarece } {sensul de parcurgere este cel trigonometric } end; { conditiile multiple ce urmeaza nu sunt } { decat implementarea cazurilor expuse in } { articol, in functie de AxB si de varfurile } { vectorilor variabila sign contine semnul } { lui AxB, iar in functie de valorile lui } { BA si AB se stabilesc celelalte conditii } if (Sign=0) and not AB and not BA then begin if inflag=Pin then Avanseaza(mb,b,b1,m,inflag=Qin,Q[b]) { la apelarea functiei avanseaza s-a } { folosit un mic artificiu de programare, } { pentru a putea scrie functia cat mai } { compact, fara conditii pentru fiecare } { poligon in parte, diferind doar modul de } { apleare, in functie de poligon } else Avanseaza(na,a,a1,n,inflag=Pin,P[a]) end
else if Sign>=0 then begin if AB then Avanseaza(na,a,a1,n, inflag=Pin,P[a]) else Avanseaza(mb,b,b1,m, inflag=Qin,Q[b]) end else begin if BA then Avanseaza(mb,b,b1,m, inflag=Qin,Q[b]) else Avanseaza(na,a,a1,n,inflag=Pin, P[a]) end; Setcolor(green); linie(aa[0][0],aa[0][1],aa[1][0],aa[1][1]); Setcolor(red); linie(bb[0][0],bb[0][1],bb[1][0],bb[1][1]); { se reprezinta grafic pasul facut } until (na>2*n)or(mb>2*m)or((mb>m)and(na>n)); writeSol; { se afiseaza solutia } for i:=0 to ns do begin sol[i][0]:=639*(1-(maxx-sol[i][0])/ (maxx-minx)); sol[i][1]:=479*(1-(maxy-sol[i][1])/ (maxy-miny)); polypoints[i][0]:=Trunc(sol[i][0]); polypoints[i][1]:=479-Trunc(sol[i][1]) end; { se convertesc coordonatele poligonului } { solutie la coordonate ecran } Fillpoly(ns,PolyPoints); { se vizualizeaza intersectia poligoanelor } Readln; Closegraph { inchiderea modului grafic } end;
Begin ReadData; Rezolva End.
Bibliografia: 1. Joseph O. Rourke - Computational Geometry in C, Cambridge University Press 1993 2. Ion D. Teodorescu - Geometrie analitică și elemente de algebră liniară, Editura didactică și pedagogică București, 1972
Angel Proorocu este elev în clasa a XII-a la Liceul de Informatică din București. Poate fi contactat prin e-mail la psangel@pcnet.pcnet.ro [cuprins] |