BG/BRG/BORG Eisenstadt -1- Winkelfunktionen in Delphi Inhalt: Grundlagen ...................................................................................................................................................................2 1) Koordinaten bei gegebenem Winkel errechnen .................................................................................................2 2) Winkel bei gegebenen Koordinaten errechnen ..................................................................................................3 Anwendungsbeispiele ...................................................................................................................................................4 1) Einheitskreis .......................................................................................................................................................4 2) Halbkreisförmiges Tortendiagramm ...................................................................................................................7 Sektor im Tortendiagramm zeichnen Variante 1: ................................................................................8 Sektor zeichnen Variante 2 (ohne Umrechnen ins Bogenmaß): ..........................................................8 Aufruf beider Varianten: ......................................................................................................................9 3) Uhr ......................................................................................................................................................................9 BG/BRG/BORG Eisenstadt -2- Winkelfunktionen in Delphi Winkelfunktionen in Delphi Grundlagen Winkelfunktionen werden immer dann benötigt, wenn man Koordinaten auf einem Kreisbogen oder einen Winkel aus gegebenen Koordinaten ermitteln möchte (z.B. Drehen eines Objekts oder Zeichnen von Sektoren). Um alle Winkelfunktionen verwenden zu können, muss unter „uses“ die Unit „Math“ eingebunden werden: uses Windows, (...), Math; Winkelfunktionen erwarten Winkelangaben generell im „Bogenmaß“. Das Bogenmaß ist definiert als Verhältnis von Kreisbogenlänge und Radius: b . Da der Kreisumfang mit der Formel 2r errechnet r wird, ergibt sich für einen vollen 360°-Kreis im Bogenmaß 90° entsprechen 2 2r 2 . 180° entsprechen somit , r . Muss von einem Maß ins andere umgerechnet werden, verwendet man die Funktionen DegToRad() bzw. RadToDeg(). 1) Koordinaten bei gegebenem Winkel errechnen Die Zeiger einer Uhr sind auf den ersten Blick nicht schwer zu programmieren: MoveTo und LineTo zeichnen bekanntlich eine Linie. Als Startkoordinaten übergibt man MoveTo den Mittelpunkt der Uhr, aber woher bekomme ich die Endkoordinaten für LineTo? Genau hier helfen uns die Winkelfunktionen: die x-Koordinate können wir mit Hilfe des Cosinus berechnen, die y-Koordinate mit Hilfe des Sinus: y = sin() x = cos() Die entsprechenden Delphi-Funktionen sin() und cos() erwarten in Klammer den Winkel im Bogenmaß. Allerdings sind zwei Dinge zu beachten: a) Sinus und Cosinus liefern noch nicht die tatsächlichen Koordinaten, sondern nur Werte zwischen -1 und +1 (nämlich das Verhältnis zwischen Hypotenuse und Ankathete bzw. Gegenkathete). Um die Koordinaten zu erhalten, muss mit dem Radius multipliziert werden. b) Unser cartesisches Koordinatensystem besitzt seinen Ursprung in der Mitte, die DelphiKomponenten, in denen wir zeichnen, aber links oben. D.h. im cartesischen Koordinatensystem BG/BRG/BORG Eisenstadt -3- Winkelfunktionen in Delphi wächst y nach oben, in Delphikomponenten nach unten. Die fertige y-Koordinate, die wir für die Ausgabe in Delphi verwenden, ergibt sich daher, indem von Mittelpunkt subtrahiert wird; die xKoordinate ergibt sich, indem zum Mittelpunkt addiert wird: y:=Mittelpunkt-sin(AktWinkel)*Radius; x:=Mittelpunkt+cos(AktWinkel)*Radius; 2) Winkel bei gegebenen Koordinaten errechnen Für die Berechnung eines Winkels aus gegebenen Koordinaten sind zwei Winkelfunktionen notwendig: Tangens und Arcus-Tangens. Tangens ist definiert als das Verhältnis von Gegenkathete und Ankathete: tan() = y . Arcus-Tangens ist die Umkehrfunktion dazu (entspricht „tan-1“ auf dem x Taschenrechner). Zuerst wird der Tangens aus den beiden Koordinaten berechnet: AktTan:=y/x; Anschließend aus diesem Tangens mit der Funktion „ArcTan“ der Winkel: Winkel:=ArcTan(AktTan); Zuvor müssen wir allerdings die beiden Spezialfälle (90° und 270°) abfangen, in denen x = 0. Eine Division durch 0 ist nicht möglich und würde unser Programm mit einer Fehlermeldung beenden. Für diese Winkel beträgt der Wert des Tangens „unendlich“. Da wir aber irgendeine gültige Zahl eintragen müssen, wählen wir einfach einen willkürlich gewählten hohen Näherungswert, z.B. „350“ (entspricht 59°59’). if x = 0 then begin if y > 0 then AktTan:=350 else AktTan:=-350; end; Zuletzt gilt es noch herauszufinden, in welchem Quadrant sich der Winkel befindet, denn darüber verrät uns Tangens alleine nicht genug. Hier die vier Möglichkeiten am Beispiel 45°/135°/225°/315° (Tangens = ±1): 45°: tan = +1 ArcTan = +45° x>0 y>0 135:° tan = -1 ArcTan = -45° x<0 y>0 225°: tan = +1 ArcTan = +45° x<0 y<0 315°: tan = -1 ArcTan = -45° x>0 y<0 Anhand der x-/y-Koordinaten kann mittels „if“-Abfragen der genaue Winkel ermittelt werden: 2.Quadrant 1.Quadrant 135°, tan = -1 45°, tan = +1 225° 3.Quadrant tan = +1 315° 4.Quadrant tan = -1 BG/BRG/BORG Eisenstadt -4- Winkelfunktionen in Delphi 1) Falls x < 0, muss er links, also im 2. oder 3.Quadrant liegen. In beiden Fällen braucht nur 180° zum Winkel addiert zu werden (180+-45=135 bzw. 180+45=225). 2a) Falls dagegen x > 0, liegt der Winkel rechts, also im 1. oder 4.Quadrant. 2b) Wenn y > 0 (4.Quadrant), addieren wir 360° zum Winkel (360+-45=315). 2c) Andernfalls (1.Quadrant) ändern wir gar nichts: der von Arcus-Tangens gelieferte Winkel ist bereits der gesuchte. Der Übersichtlichkeit halber werden die Winkel im folgenden Listing ins Gradmaß umgerechnet: Winkel:=RadToDeg(ArcTan(AktTan)); {1.Quadrant} if x < 0 then Winkel:=180+Winkel else begin{2. bzw. 3.Quadrant} if y < 0 then Winkel:=360+Winkel; {4.Quadrant} end; Anwendungsbeispiele 1) Einheitskreis uses Math; type var Form1: TForm1; AktSin,AktCos,AktTan:extended; xAlt,yAlt:extended; {für MouseMove} Winkel,Radius:integer; Mx,My:integer; {Mittelpunkt des Kreises} Rand:integer=20; {Freiraum zwischen Kreis und Rand der Paintbox (Platz für Tangens)} Zeichnen:boolean; {für MouseMove: nur zeichnen, wenn Maustaste gedrückt} procedure TForm1.FormCreate(Sender: TObject); begin Zeichnen:=False; Radius:=pbox1.Width div 2-Rand; Winkel:=tbWinkel.Position; edWinkel.Text:=IntToStr(Winkel); Mx:=pbox1.Width div 2; My:=pbox1.Height div 2; Berechnen; end; procedure TForm1.Berechnen; begin AktSin:=sin(DegToRad(Winkel)); AktCos:=cos(DegToRad(Winkel)); if ((Winkel = 90) or (Winkel = 270)) then AktTan:=350 else AktTan:=tan(DegToRad(Winkel)); end; procedure TForm1.AllesNeuzeichnen; begin with pbox1.canvas do begin Brush.Color:=clWhite; Pen.Style:=psClear; Rectangle(0,0,pbox1.Width,pbox1.Height); Pen.Style:=psSolid; Pen.Color:=clBlack; BG/BRG/BORG Eisenstadt -5- Winkelfunktionen in Delphi Pen.Width:=1; ellipse(Mx-Radius,My-Radius,pbox1.Width-(Mx-Radius),pbox1.Height-(My-Radius)); Pen.Style:=psDot; {Koordinatensystem: x- und y-Achse punktiert zeichnen} MoveTo(Mx,0); {Linie für y-Koordinaten} LineTo(Mx,pbox1.Height); MoveTo(0,My); {Linie für x-Koordinaten} LineTo(pbox1.Width,My); WinkelZeichnen; lbCart.Caption:=FloatToStrF(AktCos,ffFixed,10,2)+' / '+FloatToStrF(AktSin,ffFixed,10,2); edWinkel.Text:=IntToStr(Winkel); end; end; procedure TForm1.WinkelZeichnen; var s:string; xCos,ySin,yTan:integer; begin with pbox1.canvas do begin xCos:=Mx+round(AktCos*Radius); ySin:=My-round(AktSin*Radius); yTan:=My-round(AktTan*Radius); if cbSin.Checked then begin Pen.Style:=psSolid; Pen.Color:=clRed; Pen.Width:=2; MoveTo(xCos,My); LineTo(xCos,ySin); str(AktSin:1:3,s); lbSin.Caption:=s; end; {fall Sin checked} if cbCos.Checked then begin Pen.Style:=psSolid; Pen.Color:=clLime; Pen.Width:=2; MoveTo(Mx,My); LineTo(xCos,My); str(AktCos:1:3,s); lbCos.Caption:=s; end; {fall Cos checked} if cbTan.Checked then begin Pen.Style:=psDot; Pen.Color:=clBlue; Pen.Width:=1; MoveTo(xCos,ySin); LineTo(Mx+Radius,yTan); {punktierte Verlängerung der Hypotenuse} Pen.Style:=psSolid; Pen.Width:=2; MoveTo(Mx+Radius,My); LineTo(Mx+Radius,yTan); if (Winkel = 90) or (Winkel = 270) then lbTan.Caption:='unendlich' else begin str(AktTan:4:3,s); lbTan.Caption:=s; end; {else: Winkel <> 90, 270} end; {fall Tan checked} BG/BRG/BORG Eisenstadt -6- Winkelfunktionen in Delphi Pen.Style:=psSolid; {Hypotenuse zeichnen} Pen.Color:=clBlack; Pen.Width:=1; MoveTo(Mx,My); LineTo(xCos,ySin); end; {with pbox1.Canvas} end; procedure TForm1.pbox1Paint(Sender: TObject); begin AllesNeuzeichnen; end; procedure TForm1.edWinkelKeyPress(Sender: TObject; var Key: Char); var i,Code:integer; begin if key=#13 then begin val(edWinkel.Text,i,Code); key:=#0; if Code <> 0 then ShowMessage('Keine gültige Zahl!') else begin Winkel:=i; Berechnen; AllesNeuzeichnen; end; {else: gültige Zahl} end; {falls "Enter"} end; procedure TForm1.cbSinClick(Sender: TObject); begin AllesNeuzeichnen; end; procedure TForm1.WinkelBerechnen(x1,y1:Extended); begin if x1 = 0 then begin if y1 > 0 then AktTan:=350 else AktTan:=-350; end else AktTan:=y1/x1; Winkel:=round(RadToDeg(ArcTan(AktTan))); if x1 < 0 then Winkel:=180+Winkel else {2. oder 3.Quadrant} if y1 < 0 then Winkel:=360+Winkel; {4.Quadrant} Berechnen; AllesNeuzeichnen; end; procedure TForm1.pbox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var x1,y1:extended; begin Zeichnen:=True; x1:=x-Mx; y1:=My-y; xAlt:=x1; yAlt:=y1; WinkelBerechnen(x1,y1); end; procedure TForm1.pbox1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Zeichnen:=False; end; procedure TForm1.pbox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); BG/BRG/BORG Eisenstadt -7- Winkelfunktionen in Delphi var x1,y1:extended; begin if Zeichnen then begin x1:=x-Mx; y1:=My-y; if (x1 <> xAlt) or (y1 <> yAlt) then begin xAlt:=x1; yAlt:=y1; WinkelBerechnen(x1,y1); end; {falls bewegt} end; {if Zeichnen} end; procedure TForm1.tbWinkelChange(Sender: TObject); begin Winkel:=tbWinkel.Position; Berechnen; AllesNeuzeichnen; end; 2) Halbkreisförmiges Tortendiagramm uses Math; type TDaten=record Radius:integer; Mx,My:integer; x1,x2,y1,y2,Kx1,Ky1,Kx2,Ky2:integer; LetzterWinkel,Winkel:extended; end; const StGes=4909645; MGes=183; GproM=180/MGes; MOvp=79; MSpo=69; MFpo=18; MGr=17; MDiv=0; StOvp=2076833; StSpo=1792499; StFpo=491328; StGr=464980; StDiv=35922; var Form1: TForm1; AntOvp,AntSpo,AntFpo,AntGr,AntDiv:extended; {Stimmanteil in Prozent} Daten: TDaten; procedure TForm1.FormCreate(Sender: TObject); begin with Daten do begin Mx:=pbox1.Width div 2; {Mittelpunkt des Halbkreises} My:=pbox1.Height; {y-Koord des Mittelpunktes fast ganz unten} Radius:=Mx; {halbe Paintbox-Breite minus Rand auf beiden Seiten} Kx1:=0; {Koord des Rechtecks, in das Halbkreis gezeichnet wird} Ky1:=0; Kx2:=pbox1.Width; Ky2:=pbox1.Height*2; end; end; BG/BRG/BORG Eisenstadt -8- Winkelfunktionen in Delphi procedure TForm1.Berechnen; begin AntOvp:=(StOvp/StGes)*100; AntSpo:=(StSpo/StGes)*100; AntFpo:=(StFpo/StGes)*100; AntGr:=(StGr/StGes)*100; AntDiv:=(StDiv/StGes)*100; lbAntOvp.Caption:=FloatToStrF(AntOvp,ffFixed,6,2); lbAntSpo.Caption:=FloatToStrF(AntSpo,ffFixed,6,2); lbAntFpo.Caption:=FloatToStrF(AntFpo,ffFixed,6,2); lbAntGr.Caption:=FloatToStrF(AntGr,ffFixed,6,2); lbAntDiv.Caption:=FloatToStrF(AntDiv,ffFixed,6,2); end; procedure TForm1.bDiagrammClick(Sender: TObject); begin Diagramm; end; procedure TForm1.pbox1Paint(Sender: TObject); {innere Paintbox ohne Rand} begin with pbox1.Canvas do begin Brush.Color:=clWhite; Pen.Style:=psClear; Rectangle(0,0,pbox1.Width,pbox1.Height); end; end; Sektor im Tortendiagramm zeichnen Variante 1: procedure TForm1.Zeichnen(M:integer;Col:TColor); var WinkelRad:extended; {Hilfsvariable für Winkel im Radialmaß - zur besseren Übersichtlichkeit} begin with Daten do begin with pbox1.Canvas do begin x1:=x2; {Endkoordinaten des vorhergehenden Sektors = ...} y1:=y2; {... = Startkoord des aktuellen} Winkel:=LetzterWinkel+M*GproM; {Mandate * Grad pro Mandat = Winkel} WinkelRad:=DegToRad(Winkel); x2:=Mx+round(cos(WinkelRad)*Radius); y2:=My-round(sin(WinkelRad)*Radius); Brush.Color:=Col; Pie(Kx1,Ky1,Kx2,Ky2,x1,y1,x2,y2); LetzterWinkel:=Winkel; end; {with pbox1.Canvas} end; {with Daten} end; Sektor zeichnen Variante 2 (ohne Umrechnen ins Bogenmaß): procedure TForm1.Zeichnen(M:integer;Col:TColor); var WinkelRad:extended; {Hilfsvariable für Winkel im Radialmaß - zur besseren Übersichtlichkeit} begin with Daten do begin with pbox1.Canvas do begin x1:=x2; {Endkoordinaten des vorhergehenden Sektors = ...} y1:=y2; {... = Startkoord des aktuellen} Winkel:=LetzterWinkel+(M/MGes)*Pi; {180 Grad = Pi im Bogenmaß} x2:=Mx+round(cos(Winkel)*Radius); {End-Koord des Sektors berechnen} y2:=My-round(sin(Winkel)*Radius); Brush.Color:=Col; BG/BRG/BORG Eisenstadt -9- Winkelfunktionen in Delphi Pie(Kx1,Ky1,Kx2,Ky2,x1,y1,x2,y2); LetzterWinkel:=Winkel; end; {with pbox1.Canvas} end; {with Daten} end; Aufruf beider Varianten: procedure TForm1.Diagramm; begin with Daten do begin with pbox1.Canvas do begin LetzterWinkel:=0; x2:=Mx+Radius; y2:=My; Pen.Style:=psSolid; Zeichnen(MOvp,clBlack); Zeichnen(MFpo,clBlue); Zeichnen(MSpo,clRed); Zeichnen(MGr,clGreen); end; end; end; procedure TForm1.bBerechnenClick(Sender: TObject); begin Berechnen; end; 3) Uhr uses DateUtils; var Form1: TForm1; Uhr:TBitmap; Puffer:TBitmap; AktZeit:TDateTime; Mx,My,Radius:integer; ZSek,ZMin,ZStd:integer; {Subtrahend zu "Radius" für die einzelnen Zeiger} procedure TForm1.pbox1Paint(Sender: TObject); begin Wiederherstellen; {Uhr-Grafik in Puffer zeichnen} Berechnen; {Zeiger-Pos berechnen und in Puffer zeichnen} Ausgabe; {Puffer in Paintbox kopieren} end; procedure TForm1.FormCreate(Sender: TObject); begin Uhr:=TBitmap.Create; Uhr.LoadFromFile('Uhr.bmp'); Puffer:=TBitmap.Create; Puffer.Width:=Uhr.Width; Puffer.Height:=Uhr.Height; AktZeit:=Time; Radius:=pbox1.Width div 2; Mx:=pbox1.Width div 2; My:=pbox1.Height div 2; ZSek:=0; ZMin:=10; ZStd:=50; end; procedure TForm1.Wiederherstellen; begin Puffer.Canvas.draw(0,0,Uhr); end; procedure TForm1.ZeigerZeichnen(w:extended;z:integer); var BG/BRG/BORG Eisenstadt - 10 - Winkelfunktionen in Delphi x,y:integer; begin with Puffer.Canvas do begin x:=Mx+round(cos(W)*(Radius-z));y:=My+round(sin(W)*(Radiusz));MoveTo(Mx,My);LineTo(x,y); end; end; procedure TForm1.Berechnen; var Sek,Min,Std:integer; Winkel,Add:extended; begin Sek:=SecondOf(AktZeit); Winkel:=pi*2/60*Sek-pi/2; {360 Grad = 2*pi; um 45 Grad nach links drehen (12 Uhr = oben, nicht rechts liegend!)} ZeigerZeichnen(Winkel,ZSek); Min:=MinuteOf(AktZeit); Add:=pi*2/60/60*Sek; {Addend für M-zeiger entsprechend momentaner Pos des Sekzeigers} Winkel:=(pi*2/60*Min)+Add-pi/2; ZeigerZeichnen(Winkel,ZMin); Std:=HourOf(AktZeit); Add:=pi*2/12/60*Min; {Addend für Stundenzeiger} Winkel:=pi*2/12*Std+Add-pi/2; {24 Std = 2 Umdrehungen!} ZeigerZeichnen(Winkel,ZStd); lbAusgabe.Caption:=TimeToStr(AktZeit); lbAusgabeDatum.Caption:=DateToStr(Date); end; procedure TForm1.Ausgabe; begin pbox1.Canvas.draw(0,0,Puffer); end; procedure TForm1.Timer1Timer(Sender: TObject); begin AktZeit:=Time; Wiederherstellen; {Uhr-Grafik in Puffer zeichnen} Berechnen; {Zeiger-Pos berechnen und in Puffer zeichnen} Ausgabe; {Puffer in Paintbox kopieren} end;