Delphi - Winkelfunktionen

Werbung
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;
Herunterladen