1 Boolesche Operationen in 2D Bevor die komplizierten Operationen mit 3 dimensionalen Körpern gebracht werden, soll das Prinzip am Beispiel von 2 Flächen demonstriert werden. Die 2D Flächen seien Polygone, die durch einen geschlossenen Kantenzug gegeben sind. Das Prinzip lässt sich später auf Flächen, deren Berandung auch Kreisbogen enthält erweitern. Die betrachteten Boolesche Operationen seien: Vereinigung Durchschnitt Differenz Das Ergebnis ist evtl. ein Polygon mit Löchern oder auch mehrere disjunkte Polygone. Daher wird für eine Fläche folgende Deklaration genommen: type tface=record x,y:trealarray; loops:tintarray; nloops:integer; {loops gibt die Anzahl der Punkte des jeweiligen loops an nloops Anzahl der loops die Punkte der loops sind in x,y jeweils hintereinander Aussenloops sind Linkspolygone Innenloops(Löcher) sind Rechtspolygone } Die Koordinaten der ersten Berandung sind in dem Bereich; x[1]..x[loops[1]], y[1]..y[loops[1]] Danach folgt in dem Bereich: x[loops[1]+1]..x[loops[1]+loops[2]], y[loops[1]+1]..y[loops[1]+loops[2]] entweder ein Loch (Rechtspolygon) oder eine 2. äußere Berandung. Mit a,b,c:tface wird durch union(a,b,c); intersection(a,b,c); difference(a,b,c); jeweils aus a und b das Ergebnis c gebildet: Vereinigung Bei union(a,b,c) enthält c folgende Kanten: aus a, die nicht in b sind: AoutB(a,b,c)
2 aus b, die nicht in a sind: AoutB(b,a,c) aus a, die b berühren: AonB(a,b,c) und in gleicher Richtung laufen C:\C A DV orlesung\v2d1.p IC B A C:\CA DV orlesung\v2d2.p IC B A Dort wo A und B einen gemeinsamen Kantenabschnitt haben, ist die Richtung entgegen gesetzt, daher erscheint dieser Teil nicht in der Vereinigung. C:\CA DV orlesung\v2d3.pic A B Hier hat der gemeinsame Kantenabschnitt die gleiche Richtung, er erscheint daher in dem Ergebnis.
C:\CAD Vorlesung\v2d5.P IC 3 Durchschnitt Bei intersection(a,b,c) enthält c folgende Kanten: aus a, die in b sind: AinB(a,b,c) aus b, die in a sind: AinB(b,a,c) aus a, die b berühren: AonB(a,b,c) und in gleicher Richtung laufen C:\CA DV orlesung\v2d4.p IC Differenz Bei difference(a,b,c) enthält c folgende Kanten: aus a, die nicht in b sind: AoutB(a,b,c) aus b, die in a sind: AinB(b,a,c), mit jeweils umgekehrter Kantenorientierung. aus a, die Kanten von b berühren und in umgekehrter Orientierung laufen: AonBminus(a,b,c)
4 C:\CADVorlesung\v2d6.PIC C:\CADV orlesung\v2d7.p IC Das Polygon, das abgezogen wird, ist vollständig in dem ersten Polygon enthalten. Daher sind alle seine Kanten in dem ersten Polygon und sie bilden mit umgekehrter Kantenrichtung ein Loch in der Ergebnisfläche. Berechnung von AoutB Um die Kantenbereiche von A außerhalb von B zu berechnen, wird jede Kante von A mit jeder Kante von B geschnitten. Dies macht eine Funktion: procedure inters2l(xk,yk,xl,yl,xm,ym,xn,yn:real; var xs,ys:real; var s,t:real; var res:integer;dofix:boolean); {res -3 nur punkt -2 parallel -1 auf gleicher geraden 0 ausserhalb k-l oder m-n 1 auf rand k-l 2 auf rand m-n 3 innerhalb beider xs,ys Schnittpunkt bei res>=0 s: Position auf k-l t: Position auf m-n bei res=-1 s: position von xk,yk auf m-n t: Position von xl,yl auf m-n } Die Linie von (xk,yk) nach (xl,yl) wird mit der Linie von (xm,ym) nach (xn,yn) geschnitten.
5 Dabei können folgende Sonderfälle auftreten: Eine der beiden Linien ist nur ein Punkt, dann ist das Ergebnis -3. Die beiden Linien liegen auf der gleichen Geraden: Ergebnis -1 Die beiden Linien sind parallel: Ergebnis -2 In den anderen Fällen existiert zumindest ein Schnittpunkt der durch die beiden Linien gegebenen Geraden, der aber evtl. außerhalb der Linien oder auf einem Randpunkt liegt. Liegt er außerhalb einer der beiden Linien: Ergebnis= 0. Liegt er auf einem Randpunkt von k->l: Ergebnis= 1. Liegt er auf einem Randpunkt von m->n: Ergebnis= 2 Liegt er innerhalb beider Linien, also ein echter Schnittpunkt: Ergebnis= 3. Auf (xs,ys) wird der Schnittpunkt übergeben. s gibt die Position des Schnittpunktes zur Linie k->l wieder: Bei s=0 liegt er auf (xk,yk), bei s=1 auf (xl,yl), bei 0<s<1 liegt er dazwischen, bei <0 und >1 außerhalb. Analog gibt t die Lage bezogen auf die Linie m->n wieder. xlk:=xl-xk;ylk:=yl-yk; xnm:=xn-xm;ynm:=yn-ym; det:=xnm*ylk-ynm*xlk; Der Wert von det ist 0, wenn die Linien parallel sind. Sind die Linien nicht parallel kann weiter gemacht werden. detinv:=1.0/det; s:=(xnm*ymk-ynm*xmk)*detinv; t:=(xlk*ymk-ylk*xmk)*detinv; Ist dofix gesetzt werden die Werte für s und t in einem Bereich um 0 bzw. 1 auf 0 bzw. 1 gesetzt. Entsprechend s und t wird das Ergebnis gesetzt und der Schnittpunkt berechnet. Eine weitere Hilfsfunktion ist: procedure inface(xxx,yyy:real;var f:tface;var res:integer); Hier wird berechnet ob der Punkt (xxx,yyy) innerhalb der Fläche f liegt. Dabei sollen auch Löcher in der Fläche f berücksichtigt werden. Daher muss (xxx,yyy) gegen alle Berandungen der Fläche f getestet werden. Der test für ein Berandung erfolgt mit der Prozedur: procedure inpoly(x,y:real;var xp,yp:trealarray;n:integer; var res:integer); {res= 0 außerhalb 1 auf Eckpunkt 2 auf Rand 3 innerhalb} Die Berandung ist gegeben durch die Punktliste (xp,yp). Für den test werden jeweils die Winkelabstände der Linien von Testpunkt (x,y) zu benachbarten Punkten des Polygons summiert. Ist der Punkt außerhalb ist die Summe 0 ist er innerhalb ist die Summe 360. Liegt er auf dem Rand ist eine Winkeldifferenz 180 oder -180.
C:\CA DVorlesung\v2d8.pic 6 Um festzustellen ob eine Berandung der Fläche eine Innen- oder Außenberandung ist, wird die Funktion: function islinkspoly(var x,y:trealarray;n:integer):boolean; var xx,yy:trealarray; i,i2,i3:integer; x0,y0,x1,y1:real; s,w:real; begin s:=0; for i:=1 to n do begin if i=n then i2:=1 else i2:=i+1; if i2=n then i3:=1 else i3:=i2+1; x0:=x[i];y0:=y[i];x1:=x[i2];y1:=y[i2]; w:=diffwinkel(0,0,x1-x0,y1-y0,x[i3]-x1,y[i3]-y1); if w>180 then w:=w-360; s:=s+w; islinkspoly:=s>=0; Hier werden die Winkeldifferenzen der Linien des Polygons summiert. Bei einem Linkspolygon ist die Summe 360 bei einem Rechtpolygon ist die Summe -360. Mit diesen Hilfsprozeduren kann inface implemeentiert werden. procedure inface(xxx,yyy:real;var f:tface;var res:integer); {res=0 außerhalb 1 auf Eckpunkt 2 auf Rand 3 innerhalb} var xx,yy:trealarray;nn:integer;
7 p,i,j:integer; links,wasinaussen:boolean; begin wasinaussen:=false; p:=0; with f do begin for i:=1 to nloops do begin nn:=loops[i]; for j:=1 to nn do begin inc(p); xx[j]:=x[p];yy[j]:=y[p]; links:=islinkspoly(xx,yy,nn); if not links or not wasinaussen then begin inpoly(xxx,yyy,xx,yy,nn,res); {res= 0 außerhalb 1 auf Eckpunkt 2 auf Rand 3 innerhalb} if (res=1)or(res=2) then exit; if links then begin if res=3 then wasinaussen:=true;// in Außenberandung end else begin if res=3 then begin res:=0;exit; // in einem Loch if wasinaussen then res:=3 else res:=0; procedure Aoutb(var a,b:tface; var x1,y1,x2,y2:trealarray;var n:integer); {Die Kanten aus a die nicht in b sind werden in den Listen x1,y1,x2,y2 hinzugefügt} var ii,i,j,jmin,i2,j2,nschnitt,res:integer; xt,yt,xxx1,yyy1,xxx2,yyy2,smin, xx,yy,xx1,yy1,xx2,yy2,s,t:real; xschnitt,yschnitt,sschnitt:trealarray; xa1,ya1,xa2,ya2:trealarray;na:integer; xb1,yb1,xb2,yb2:trealarray;nb:integer; begin face2segments(a,xa1,ya1,xa2,ya2,na); face2segments(b,xb1,yb1,xb2,yb2,nb); {Kanten von a und b in die Koordinatenfelder} {Für jede Kante von a alle Schnittpunkte mit b untersuchen} for i:=1 to na do begin nschnitt:=0; for j:=1 to nb do begin inters2l(xa1[i],ya1[i],xa2[i],ya2[i], xb1[j],yb1[j],xb2[j],yb2[j],xx,yy,s,t,res,true);
if res>=1 then begin inc(nschnitt); xschnitt[nschnitt]:=xx;yschnitt[nschnitt]:=yy; sschnitt[nschnitt]:=s; {schnitte sortieren} sortpoints(xschnitt,yschnitt,sschnitt,nschnitt); {Randschnitt und doppelte entfernen} for ii:=1 to nschnitt do begin if sschnitt[ii]<>0 then break; deletearr(xschnitt,nschnitt,ii); deletearr(yschnitt,nschnitt,ii); deletearr(sschnitt,nschnitt,ii); dec(nschnitt); for ii:=nschnitt downto 1 do begin if sschnitt[ii]<>1 then break; deletearr(xschnitt,nschnitt,ii); deletearr(yschnitt,nschnitt,ii); deletearr(sschnitt,nschnitt,ii); dec(nschnitt); ii:=1; while ii<nschnitt do begin j:=ii+1; while j<=nschnitt do begin if sschnitt[ii]<>sschnitt[j] then break; deletearr(xschnitt,nschnitt,j); deletearr(yschnitt,nschnitt,j); deletearr(sschnitt,nschnitt,j); dec(nschnitt); inc(ii); {testen welche Kantenteile in b} xxx1:=xa1[i];yyy1:=ya1[i]; if nschnitt=0 then begin xxx2:=xa2[i];yyy2:=ya2[i]; end else begin xxx2:=xschnitt[1];yyy2:=yschnitt[1] xt:=(xxx1+xxx2)/2;yt:=(yyy1+yyy2)/2; // Testen ob Mittelpunkt in b inface(xt,yt,b,res); if res=0 then begin inc(n); x1[n]:=xxx1;y1[n]:=yyy1;x2[n]:=xxx2;y2[n]:=yyy2; for ii:=1 to nschnitt do begin xxx1:=xxx2;yyy1:=yyy2; if ii=nschnitt then begin 8
9 xxx2:=xa2[i];yyy2:=ya2[i]; end else begin xxx2:=xschnitt[ii+1];yyy2:=yschnitt[ii+1] xt:=(xxx1+xxx2)/2;yt:=(yyy1+yyy2)/2; inface(xt,yt,b,res); if res=0 then begin inc(n); x1[n]:=xxx1;y1[n]:=yyy1;x2[n]:=xxx2;y2[n]:=yyy2; Jede Kante von A wird geteilt an den Schnittpunkten mit B. Für jeden Bereich wird ermittelt ob er in B liegt. Wenn ja, wird er der Linienliste hizugefügt. Analog arbeiten auch die anderen Funktionen: procedure Ainb(var a,b:tface; var x1,y1,x2,y2:trealarray;var n:integer); {Die Kanten aus a die nicht in b sind werden in den Listen x1,y1,x2,y2 hinzugefügt} procedure Aonb(var a,b:tface; var x1,y1,x2,y2:trealarray;var n:integer); {Die Kanten aus a die auf Kanten von b sind und in gleicher Richtung laufen werden in den Listen x1,y1,x2,y2 hinzugefügt} Hier müssen beim Test sich die beiden Kanten auf identischen Linien befinden und wenn sie sich überlappen und gleiche Richtung haben, wird der überlappende Teil genommen. Für die Berechnung der Differenz müssen die Kanten auf A ermittelt werden, die sich auf Kanten von B befinden, die in umgekehrter Richtung verlaufen. procedure Aonbminus(var a,b:tface; var x1,y1,x2,y2:trealarray;var n:integer); {Die Kanten aus a die auf Kanten von b sind und in umgekerter Richtung laufen werden in den Listen x1,y1,x2,y2 hinzugefügt} Die Funktion union hat dann folgenden Aufbau: procedure union(var a,b,c:tface); {c wird a vereinigt b} var ax1,ay1,ax2,ay2:trealarray;an:integer; bx1,by1,bx2,by2:trealarray;bn:integer; cx1,cy1,cx2,cy2:trealarray;cn:integer; x1,y1,x2,y2:trealarray;n:integer; begin
10 face2segments(a,ax1,ay1,ax2,ay2,an); face2segments(b,bx1,by1,bx2,by2,bn); n:=0; aoutb(a,b,x1,y1,x2,y2,n); aoutb(b,a,x1,y1,x2,y2,n); aonb(a,b,x1,y1,x2,y2,n); cn:=0; combinesegments(x1,y1,x2,y2,n,cx1,cy1,cx2,cy2,cn); segments2face(cx1,cy1,cx2,cy2,cn,c); Die Aufrufe der Prozeduren aoutb und aonb haben zusammen eine Folge von Linen geliefert. Diese müssen aber noch zu den Berandungen zusammengefasst werden. Zunächst werden Kanten, die in der gleichen Richtung laufen und sich am Anfang oder Ende berühren zu einer Kante zusammengefasst. Dies liefert die Prozedur: procedure combinesegments(var x1,y1,x2,y2:trealarray; n:integer; var xr1,yr1,xr2,yr2:trealarray; var nr:integer); Die n Kanten aus x1,y1,x2,y2 werden gesammelt und zu den Kanten in xr1,yr1,xr2,yr2 hinzugefügt. var done:array[1..maxpunkte]of boolean; phi:trealarray; phi1,xx1,yy1,xx2,yy2:real; i,j:integer; label 1; {Einsammeln der zusammenhängenden Segmente} begin for i:=1 to n do begin done[i]:=false;phi[i]:=getphi(x1[i],y1[i],x2[i],y2[i]); for i:=1 to n do begin if not done[i] then begin xx1:=x1[i];yy1:=y1[i];xx2:=x2[i];yy2:=y2[i];done[i]:=true; phi1:=phi[i]; {ein Segment in gleicher Richtung suchen das vorne oder hinten angehängt werden kann} for j:=i+1 to n do begin if not done[j] then begin if abs(phi1-phi[j])<1 then begin //gleiche Richtung if abs(xx1-x2[j])+abs(yy1-y2[j])<realtol then begin xx1:=x1[j];yy1:=y1[j];done[j]:=true; end else if abs(xx2-x1[j])+abs(yy2-y1[j])<realtol then begin xx2:=x2[j];yy2:=y2[j];done[j]:=true;
11 inc(nr); xr1[nr]:=xx1;yr1[nr]:=yy1;xr2[nr]:=xx2;yr2[nr]:=yy2; Dann müssen noch diese maximalen Kanten zu den Berandungen zusammengefasst werden. Dies liefert die Prozedur: procedure segments2face(var ax1,ay1,ax2,ay2;n:integer; var:tface); var x1:trealarray absolute ax1; var x2:trealarray absolute ax2; var y1:trealarray absolute ay1; var y2:trealarray absolute ay2; {Es wird vorausgesetzt, dass die Segmente schon zu max. kombiniert sind} var done:array[1..maxpunkte]of boolean; change,change2:boolean; firstx,firsty,xx1,yy1,xx2,yy2:real; i,np:integer; label 1; begin np:=0; for i:=1 to n do done[i]:=false; with f do begin nloops:=0; if n<2 then exit; change:=true; while change do begin {ein noch nicht bearbeitetes Segment als Anfang eines loops suchen} for i:=1 to n do begin if not done[i] then begin xx1:=x1[i];yy1:=y1[i];xx2:=x2[i];yy2:=y2[i];done[i]:=true; goto 1; break; 1: change:=true; inc(nloops);loops[nloops]:=1; firstx:=xx1;firsty:=yy1; inc(np); x[np]:=xx1;y[np]:=yy1; {ein Segment suchen das hinten angehängt werden kann } change2:=true; while change2 do begin change2:=false; for i:=1 to n do begin if not done[i] then begin
12 if abs(xx2-x1[i])+abs(yy2-y1[i])<realtol then begin inc(loops[nloops]); inc(np); x[np]:=x1[i];y[np]:=y1[i]; xx2:=x2[i];yy2:=y2[i];change2:=true; done[i]:=true; {es wird kein Segment mehr gefunden, jetzt sollte eigentlich xx2=firstx und yy2:=firsty sein} if not eqreal(xx2,firstx) or not eqreal(yy2,firsty)then writeln('error ');